JAXB erstellt Kontext- und Marshaller-Kosten

120

Die Frage ist ein bisschen theoretisch: Was kostet die Erstellung eines JAXB-Kontexts, Marshaller und Unmarshaller?

Ich habe festgestellt, dass mein Code davon profitieren kann, dass für alle Marshalling-Vorgänge derselbe JAXB-Kontext und möglicherweise derselbe Marshaller beibehalten wird, anstatt bei jedem Marshalling Kontext und Marshaller zu erstellen.

Was kostet die Erstellung eines JAXB-Kontexts und eines Marshallers / Unmarshallers? Ist es in Ordnung, für jede Marshalling-Operation Kontext + Marshaller zu erstellen, oder ist es besser, dies zu vermeiden?

Vladimir
quelle

Antworten:

244

Hinweis: Ich bin der Leiter von EclipseLink JAXB (MOXy) und Mitglied der Expertengruppe JAXB 2 ( JSR-222 ).

JAXBContextist threadsicher und sollte nur einmal erstellt und wiederverwendet werden, um die Kosten für die mehrfache Initialisierung der Metadaten zu vermeiden. Marshallerund Unmarshallersind nicht threadsicher, aber leicht zu erstellen und können pro Operation erstellt werden.

bdoughan
quelle
7
gute Antwort. Ich kann jetzt aufgrund Ihrer Erfahrung als Lead bei JAXB zuversichtlich sein.
Vladimir
7
Ich vertraue Ihnen, aber ist dies irgendwo in der Dokumentation zu finden?
Hurda
3
Es ist für die RI dokumentiert: jaxb.java.net/guide/Performance_and_thread_safety.html (aber nicht Moxy AFAIK)
Caoilte
39
Bitte geben Sie dies im Javadoc an. Es ist nicht zufriedenstellend, diese entscheidenden Aspekte nicht dokumentiert zu haben.
Thomas W
6
Im Javadoc wird nicht erwähnt, dass JAXBContext threadsicher ist. Und fast jedes Beispiel für die Verwendung von JAXB erwähnt nicht, dass es einmal erstellt und für mehrere Threads freigegeben werden sollte. Infolgedessen entferne ich jetzt ein weiteres heulendes Ressourcenleck aus einem Live-System. Grrrrrrr.
Reg Whitton
42

Idealerweise sollten Sie einen Singleton JAXBContextund lokale Instanzen von Marshallerund haben Unmarshaller.

JAXBContextInstanzen sind threadsicher, während Marshallerund UnmarshallerInstanzen nicht threadsicher sind und niemals von Threads gemeinsam genutzt werden sollten.

Sahil Muthoo
quelle
Danke für die Antwort. Leider muss ich nur eine Antwort auswählen :-)
Vladimir
15

Schade, dass dies im Javadoc nicht speziell beschrieben wird. Was ich sagen kann ist, dass Spring einen globalen JAXBContext verwendet, der von Threads gemeinsam genutzt wird, während für jeden Marshalling-Vorgang ein neuer Marshaller erstellt wird. Ein Javadoc-Kommentar im Code besagt, dass JAXB-Marshaller nicht unbedingt threadsicher sind.

Das Gleiche gilt auf dieser Seite: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-miscellaneous-topics-performance-and-thread-safety .

Ich würde vermuten, dass das Erstellen eines JAXBContext eine kostspielige Operation ist, da Klassen und Pakete nach Anmerkungen durchsucht werden müssen. Aber es zu messen ist der beste Weg, es zu wissen.

JB Nizet
quelle
Hallo @JB, tolle Antwort, besonders auf Ihre Kommentare zum Messen und warum JAXBContext teuer ist.
Vladimir
1
Javadoc war in Bezug auf die entscheidenden Fakten des Lebenszyklus immer schwach . Es gibt uns sicher eine triviale Wiederholung von Property Getter & Setter, aber was das Wissen betrifft, wie / wo eine Instanz zu erhalten oder zu erstellen ist, Mutation & Thread-Sicherheit. Es scheint diese wichtigsten Faktoren völlig zu übersehen. Seufz :)
Thomas W
4

JAXB 2.2 ( JSR-222 ) hat in Abschnitt "4.2 JAXBContext" Folgendes zu sagen:

Um den mit dem Erstellen einer JAXBContextInstanz verbundenen Aufwand zu vermeiden , wird einer JAXB-Anwendung empfohlen, eine JAXBContext-Instanz wiederzuverwenden . Eine Implementierung der abstrakten Klasse JAXBContext muss threadsicher sein. Daher können mehrere Threads in einer Anwendung dieselbe JAXBContext-Instanz gemeinsam nutzen.

[..]

Die JAXBContext-Klasse ist so konzipiert, dass sie unveränderlich und damit threadsicher ist. Angesichts des Umfangs der dynamischen Verarbeitung, die möglicherweise beim Erstellen einer neuen Instanz von JAXBContxt stattfinden kann, wird empfohlen, eine JAXBContext-Instanz für mehrere Threads gemeinsam zu nutzen und so weit wie möglich wiederzuverwenden, um die Anwendungsleistung zu verbessern.

Leider erhebt die Spezifikation keinen Anspruch auf Gewindesicherheit von Unmarshallerund Marshaller. Es ist also am besten anzunehmen, dass dies nicht der Fall ist.

Martin Andersson
quelle
3

Ich habe dieses Problem gelöst mit:

  • Shared Thread Safe JAXBContext und Thread Local Un / Marschaller
  • (Theoretisch wird es also so viele Un / Marshaller- Instanzen geben, wie es Threads gibt, die auf sie zugegriffen haben.)
  • mit Synchronisation nur bei der Initialisierung von un / marshaller .
public class MyClassConstructor {
    private final ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<Unmarshaller>() {
        protected synchronized Unmarshaller initialValue() {
            try {
                return jaxbContext.createUnmarshaller();
            } catch (JAXBException e) {
                throw new IllegalStateException("Unable to create unmarshaller");
            }
        }
    };
    private final ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<Marshaller>() {
        protected synchronized Marshaller initialValue() {
            try {
                return jaxbContext.createMarshaller();
            } catch (JAXBException e) {
                throw new IllegalStateException("Unable to create marshaller");
            }
        }
    };

    private final JAXBContext jaxbContext;

    private MyClassConstructor(){
        try {
            jaxbContext = JAXBContext.newInstance(Entity.class);
        } catch (JAXBException e) {
            throw new IllegalStateException("Unable to initialize");
        }
    }
}
peeeto
quelle
8
ThreadLocal wird andere subtile Probleme ohne Nutzen einführen. Behalten Sie einfach einen einzelnen JAXBContext (das ist der kostspielige Teil) und erstellen Sie bei Bedarf einen neuen Unmarshaller.
Ymajoros
2

Noch besser!! Erstellen Sie den Kontext basierend auf der guten Lösung aus dem obigen Beitrag nur einmal im Konstruktor und speichern Sie ihn anstelle der Klasse.

Ersetzen Sie die Leitung:

  private Class clazz;

mit diesem:

  private JAXBContext jc;

Und der Hauptkonstruktor mit diesem:

  private Jaxb(Class clazz)
  {
     this.jc = JAXBContext.newInstance(clazz);
  }

Im getMarshaller / getUnmarshaller können Sie diese Zeile entfernen:

  JAXBContext jc = JAXBContext.newInstance(clazz);

Diese Verbesserung führt in meinem Fall dazu, dass die Verarbeitungszeiten von 60 bis 70 ms auf nur 5 bis 10 ms sinken

tbarderas
quelle
Wie groß war die XML-Datei, die Sie analysiert haben? Sehen Sie eine signifikante Verbesserung bei sehr großen XML-Dateien?
John
1
Es geht nicht wirklich um große XML-Dateien (Minen gehen von nur 2-3 KB auf + 6 MB), sondern um eine sehr große Anzahl von XML-Dateien (wir sprechen hier von ungefähr 10.000 XML-Anfragen pro Minute). In diesem Fall macht es einen großen Unterschied, den Kontext nur einmal zu erstellen, um diese kleinen ms zu erhalten
tbarderas
1

Normalerweise löse ich solche Probleme mit einem ThreadLocalKlassenmuster. Da Sie für jede Klasse einen anderen Marshaller benötigen, können Sie ihn mit einem singletonKartenmuster kombinieren .

Um Ihnen 15 Minuten Arbeit zu sparen. Hier folgt meine Implementierung einer thread-sicheren Factory für Jaxb Marshallers und Unmarshallers.

Sie können wie folgt auf die Instanzen zugreifen ...

Marshaller m = Jaxb.get(SomeClass.class).getMarshaller();
Unmarshaller um = Jaxb.get(SomeClass.class).getUnmarshaller();

Und der Code, den Sie benötigen, ist eine kleine Jaxb-Klasse, die wie folgt aussieht:

public class Jaxb
{
  // singleton pattern: one instance per class.
  private static Map<Class,Jaxb> singletonMap = new HashMap<>();
  private Class clazz;

  // thread-local pattern: one marshaller/unmarshaller instance per thread
  private ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<>();
  private ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<>();

  // The static singleton getter needs to be thread-safe too, 
  // so this method is marked as synchronized.
  public static synchronized Jaxb get(Class clazz)
  {
    Jaxb jaxb =  singletonMap.get(clazz);
    if (jaxb == null)
    {
      jaxb = new Jaxb(clazz);
      singletonMap.put(clazz, jaxb);
    }
    return jaxb;
  }

  // the constructor needs to be private, 
  // because all instances need to be created with the get method.
  private Jaxb(Class clazz)
  {
     this.clazz = clazz;
  }

  /**
   * Gets/Creates a marshaller (thread-safe)
   * @throws JAXBException
   */
  public Marshaller getMarshaller() throws JAXBException
  {
    Marshaller m = marshallerThreadLocal.get();
    if (m == null)
    {
      JAXBContext jc = JAXBContext.newInstance(clazz);
      m = jc.createMarshaller();
      marshallerThreadLocal.set(m);
    }
    return m;
  }

  /**
   * Gets/Creates an unmarshaller (thread-safe)
   * @throws JAXBException
   */
  public Unmarshaller getUnmarshaller() throws JAXBException
  {
    Unmarshaller um = unmarshallerThreadLocal.get();
    if (um == null)
    {
      JAXBContext jc = JAXBContext.newInstance(clazz);
      um = jc.createUnmarshaller();
      unmarshallerThreadLocal.set(um);
    }
    return um;
  }
}
bvdb
quelle
10
ThreadLocal wird andere subtile Probleme ohne Nutzen einführen. Behalten Sie einfach einen einzelnen JAXBContext (das ist der kostspielige Teil) und erstellen Sie bei Bedarf einen neuen Unmarshaller.
Ymajoros
Sie benötigen keine separaten JAXBContexts, da Sie mehrere Klassen übergeben können. Wenn Sie also vorhersagen können, welche Klassen gemarshallt werden, können Sie eine einzelne gemeinsam genutzte Klasse erstellen. Außerdem erfordert die JAXB-Spezifikation, dass sie bereits threadsicher sind.
MauganRa