JAXB creando contexto y costos de ordenadores

120

La pregunta es un poco teórica, ¿cuál es el costo de crear un contexto JAXB, marshaller y unmarshaller?

Descubrí que mi código podría beneficiarse de mantener el mismo contexto JAXB y posiblemente el mismo marshaller para todas las operaciones de clasificación en lugar de crear contexto y marshaller en cada clasificación.

Entonces, ¿cuál es el costo de crear contexto JAXB y marshaller / unmarshaller? ¿Está bien crear context + marshaller para cada operación de ordenación o es mejor evitarlo?

Vladimir
fuente

Respuestas:

244

Nota: Soy el líder de EclipseLink JAXB (MOXy) y miembro del grupo de expertos JAXB 2 ( JSR-222 ).

JAXBContextes seguro para subprocesos y solo debe crearse una vez y reutilizarse para evitar el costo de inicializar los metadatos varias veces. Marshallery Unmarshallerno son seguros para subprocesos, pero son livianos de crear y podrían crearse por operación.

bdoughan
fuente
7
gran respuesta. Ahora puedo estar seguro de su experiencia como líder en JAXB.
Vladimir
7
Confío en usted, pero ¿se encuentra en algún lugar de la documentación?
Hurda
3
Está documentado para el RI: jaxb.java.net/guide/Performance_and_thread_safety.html (pero no Moxy AFAIK)
Caoilte
39
Especifique esto en el Javadoc. No es satisfactorio tener estos aspectos cruciales indocumentados.
Thomas W
6
No se menciona en el Javadoc que JAXBContext es seguro para subprocesos. Y casi todos los ejemplos de cómo usar JAXB no mencionan que debe crearse una vez y compartirse entre subprocesos. Como resultado, ahora estoy eliminando otra fuga de recursos de un sistema en vivo. Grrrrrrr.
Reg Whitton
42

Idealmente, debería tener un singleton JAXBContexte instancias locales de Marshallery Unmarshaller.

JAXBContextinstancias son thread-safe mientras Marshallery Unmarshallercasos son no thread-safe y nunca deben ser compartidos a través de las roscas.

Sahil Muthoo
fuente
gracias por la respuesta. Desafortunadamente, solo tengo que seleccionar una respuesta :-)
Vladimir
15

Es una pena que esto no se describa específicamente en el javadoc. Lo que puedo decir es que Spring usa un JAXBContext global, compartido entre subprocesos, mientras que crea un nuevo marshaller para cada operación de marshalling, con un comentario javadoc en el código que dice que los marshallers de JAXB no son necesariamente seguros para subprocesos.

Lo mismo se dice en esta página: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-mistionary-topics-performance-and-thread-safety .

Supongo que crear un JAXBContext es una operación costosa, porque implica escanear clases y paquetes en busca de anotaciones. Pero medirlo es la mejor manera de saberlo.

JB Nizet
fuente
Hola @JB, excelente respuesta, especialmente sus comentarios sobre la medición y por qué JAXBContext es costoso.
Vladimir
1
Javadoc siempre ha sido débil en los hechos cruciales del ciclo de vida . Seguro que nos da una repetición trivial de captadores y definidores de propiedades, pero en cuanto a saber cómo / dónde obtener o crear una instancia, mutación y seguridad de subprocesos ... parece pasar por alto por completo esos factores más importantes. Suspiro :)
Thomas W
4

JAXB 2.2 ( JSR-222 ) tiene esto que decir, en la sección "4.2 JAXBContext":

Para evitar la sobrecarga que implica la creación de una JAXBContextinstancia, se recomienda a una aplicación JAXB que reutilice una instancia JAXBContext . Se requiere una implementación de la clase abstracta JAXBContext para ser segura para subprocesos , por lo tanto, varios subprocesos en una aplicación pueden compartir la misma instancia de JAXBContext.

[..]

La clase JAXBContext está diseñada para ser inmutable y, por lo tanto, segura para subprocesos. Dada la cantidad de procesamiento dinámico que potencialmente podría tener lugar al crear una nueva instancia de JAXBContxt, se recomienda que una instancia de JAXBContext se comparta entre subprocesos y se reutilice tanto como sea posible para mejorar el rendimiento de la aplicación.

Desafortunadamente, la especificación no hace ninguna afirmación con respecto a la seguridad de subprocesos de Unmarshallery Marshaller. Por lo que es mejor asumir que no lo son.

Martin Andersson
fuente
3

Resolví este problema usando:

  • JAXBContext seguro para subprocesos compartidos y un / marschallers local de subprocesos
  • (entonces, teóricamente, habrá tantas instancias un / marshaller como subprocesos que accedieron a ellas)
  • con sincronización solo en la inicialización de 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
fuente
8
ThreadLocal introducirá otros problemas sutiles, sin beneficio. Solo mantenga un solo JAXBContext (esa es la parte costosa) y cree un nuevo Unmarshaller cuando sea necesario.
ymajoros
2

¡¡Aun mejor!! Según la buena solución de la publicación anterior, cree el contexto solo una vez en el constructor y guárdelo en lugar de la clase.

Reemplazar la línea:

  private Class clazz;

Con este:

  private JAXBContext jc;

Y el constructor principal con este:

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

entonces en getMarshaller / getUnmarshaller puede eliminar esta línea:

  JAXBContext jc = JAXBContext.newInstance(clazz);

Esta mejora hace, en mi caso, que los tiempos de procesamiento bajen de 60 ~ 70ms a solo 5 ~ 10ms

tbarderas
fuente
¿Qué tamaño tenía el archivo xml que estaba analizando? ¿Ve una mejora significativa con archivos xml muy grandes?
Juan
1
no se trata realmente de grandes archivos xml (las minas van desde solo 2-3kb hasta + 6mb), sino que se trata de una gran cantidad de archivos xml (estamos hablando aquí de unas 10.000 solicitudes xml por minuto); en ese caso la creación del contexto sólo una vez ganando esos pequeños ms hace una gran diferencia
tbarderas
1

Normalmente resuelvo problemas como este con un ThreadLocalpatrón de clase. Dado que necesita un marshaller diferente para cada clase, puede combinarlo con un singletonpatrón -map.

Para ahorrarle 15 minutos de trabajo. Aquí sigue mi implementación de una fábrica segura para subprocesos para Jaxb Marshallers y Unmarshallers.

Le permite acceder a las instancias de la siguiente manera ...

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

Y el código que necesitará es una pequeña clase Jaxb que tiene el siguiente aspecto:

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
fuente
10
ThreadLocal introducirá otros problemas sutiles, sin beneficio. Solo mantenga un solo JAXBContext (esa es la parte costosa) y cree un nuevo Unmarshaller cuando sea necesario.
ymajoros
En realidad, no necesita JAXBContexts separados, ya que puede pasar varias clases. Por lo tanto, si puede predecir qué clases se clasificarán, puede crear una única compartida. Además, la especificación JAXB requiere que ya sean seguros para subprocesos.
MauganRa