Tengo una aplicación Java ee bastante grande con un gran classpath que hace mucho procesamiento xml. Actualmente estoy tratando de acelerar algunas de mis funciones y localizar rutas de código lentas a través de perfiladores de muestreo.
Una cosa que noté es que especialmente las partes de nuestro código en las que tenemos llamadas TransformerFactory.newInstance(...)
son extremadamente lentas. Seguí esto hasta el FactoryFinder
método, findServiceProvider
siempre creando una nueva ServiceLoader
instancia. En ServiceLoader
javadoc encontré la siguiente nota sobre el almacenamiento en caché:
Los proveedores se ubican y crean instancias de manera perezosa, es decir, bajo demanda. Un cargador de servicios mantiene un caché de los proveedores que se han cargado hasta ahora. Cada invocación del método iterador devuelve un iterador que primero produce todos los elementos de la memoria caché, en orden de creación de instancias, y luego localiza e instancia perezosamente a los proveedores restantes, agregando cada uno a la memoria caché a su vez. El caché se puede borrar mediante el método de recarga.
Hasta aquí todo bien. Esto es parte del FactoryFinder#findServiceProvider
método OpenJDKs :
private static <T> T findServiceProvider(final Class<T> type)
throws TransformerFactoryConfigurationError
{
try {
return AccessController.doPrivileged(new PrivilegedAction<T>() {
public T run() {
final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
final Iterator<T> iterator = serviceLoader.iterator();
if (iterator.hasNext()) {
return iterator.next();
} else {
return null;
}
}
});
} catch(ServiceConfigurationError e) {
...
}
}
Cada llamada a las findServiceProvider
llamadas ServiceLoader.load
. Esto crea un nuevo ServiceLoader cada vez. De esta manera, parece que no hay ningún uso del mecanismo de almacenamiento en caché de ServiceLoaders. Cada llamada escanea el classpath para el ServiceProvider solicitado.
Lo que ya he probado:
- Sé que puede establecer una propiedad del sistema
javax.xml.transform.TransformerFactory
para especificar una implementación específica. De esta manera FactoryFinder no utiliza el proceso ServiceLoader y es súper rápido. Lamentablemente, esta es una propiedad amplia de jvm y afecta a otros procesos de Java que se ejecutan en mi jvm. Por ejemplo, mi aplicación se envía con Saxon y debería usarcom.saxonica.config.EnterpriseTransformerFactory
Tengo otra aplicación que no se envía con Saxon. Tan pronto como configuro la propiedad del sistema, mi otra aplicación no se inicia porque no hay ningunacom.saxonica.config.EnterpriseTransformerFactory
en su classpath. Así que esto no parece ser una opción para mí. - Ya refactoricé todos los lugares donde
TransformerFactory.newInstance
se llama a y almacené en caché TransformerFactory. Pero hay varios lugares en mis dependencias donde no puedo refactorizar el código.
Mi pregunta es: ¿Por qué FactoryFinder no reutiliza un ServiceLoader? ¿Hay alguna manera de acelerar todo este proceso de ServiceLoader que no sea usar las propiedades del sistema? ¿No podría cambiarse esto en el JDK para que FactoryFinder reutilice una instancia de ServiceLoader? Además, esto no es específico de un único FactoryFinder. Este comportamiento es el mismo para todas las clases de FactoryFinder en el javax.xml
paquete que he visto hasta ahora.
Estoy usando OpenJDK 8/11. Mis aplicaciones se implementan en una instancia de Tomcat 9.
Editar: proporcionar más detalles
Aquí está la pila de llamadas para una sola llamada XMLInputFactory.newInstance:
Donde se utilizan la mayoría de los recursos es en ServiceLoaders$LazyIterator.hasNextService
. Este método llama getResources
a ClassLoader para leer el META-INF/services/javax.xml.stream.XMLInputFactory
archivo. Esa llamada solo toma alrededor de 35 ms cada vez.
¿Hay alguna manera de indicarle a Tomcat que guarde mejor estos archivos en caché para que se sirvan más rápido?
fuente
-D
flag en tuTomcat
proceso? Por ejemplo:-Djavax.xml.transform.TransformerFactory=<factory class>.
no debe anular las propiedades de otras aplicaciones. Su publicación está bien descrita y probablemente lo haya intentado, pero me gustaría confirmarlo. Consulte Cómo configurar la propiedad del sistema Javax.xml.transform.TransformerFactory , Cómo configurar HeapMemory o JVM Arguments en TomcatRespuestas:
35 ms parece que hay tiempos de acceso al disco involucrados, y eso apunta a un problema con el almacenamiento en caché del sistema operativo.
Si hay entradas de directorio / no jar en el classpath que pueden ralentizar las cosas. Además, si el recurso no está presente en la primera ubicación que está marcada.
ClassLoader.getResource
puede anularse si puede establecer el cargador de clases de contexto de subproceso, ya sea a través de la configuración (no he tocado tomcat durante años) o simplementeThread.setContextClassLoader
.fuente
Podría obtener otros 30 minutos para depurar esto y analicé cómo Tomcat realiza el almacenamiento en caché de recursos.
En particular
CachedResource.validateResources
(que se puede encontrar en el gráfico de llamas anterior) fue de interés para mí. Regresatrue
si elCachedResource
todavía es válido:Parece que un CachedResource realmente tiene tiempo de vivir (ttl). En realidad, hay una forma en Tomcat de configurar el cacheTtl, pero solo puede aumentar este valor. Parece que la configuración de almacenamiento en caché de recursos no es realmente flexible fácilmente.
Entonces mi Tomcat tiene el valor predeterminado de 5000 ms configurado. Esto me engañó mientras hacía pruebas de rendimiento porque tenía un poco más de 5 segundos entre mis solicitudes (mirando gráficos y otras cosas). Es por eso que todas mis solicitudes básicamente se ejecutaron sin caché y se activaron de esta manera
ZipFile.open
cada vez.Entonces, como no tengo mucha experiencia con la configuración de Tomcat, todavía no estoy seguro de cuál es la solución correcta aquí. El aumento de cacheTTL mantiene los cachés por más tiempo pero no soluciona el problema a largo plazo.
Resumen
Creo que en realidad hay dos culpables aquí.
Las clases de FactoryFinder no reutilizan un ServiceLoader. Puede haber una razón válida por la que no los reutilizan, aunque realmente no puedo pensar en uno.
Tomcat desalojando cachés después de un tiempo fijo para recursos de aplicaciones web (archivos en el classpath, como una
ServiceLoader
configuración)Combine esto con no haber definido la Propiedad del sistema para la clase ServiceLoader y obtendrá una llamada lenta de FactoryFinder cada
cacheTtl
segundo.Por ahora puedo vivir aumentando cacheTtl a un tiempo más largo. También podría echar un vistazo a la sugerencia de Tom Hawtin de anular,
Classloader.getResources
incluso si creo que esta es una forma dura de deshacerse de este cuello de botella de rendimiento. Sin embargo, podría valer la pena mirarlo.fuente