J'ai une application Java ee assez grande avec un énorme chemin de classe faisant beaucoup de traitement xml. Actuellement, j'essaie d'accélérer certaines de mes fonctions et de localiser des chemins de code lents via des profils d'échantillonnage.
Une chose que j'ai remarquée, c'est que les parties de notre code dans lesquelles nous avons des appels comme TransformerFactory.newInstance(...)
sont désespérément lentes. J'ai suivi cela jusqu'à la FactoryFinder
méthode findServiceProvider
créant toujours une nouvelle ServiceLoader
instance. En ServiceLoader
javadoc, j'ai trouvé la note suivante sur la mise en cache:
Les fournisseurs sont localisés et instanciés paresseusement, c'est-à-dire à la demande. Un chargeur de service conserve un cache des fournisseurs qui ont été chargés jusqu'à présent. Chaque invocation de la méthode itérateur renvoie un itérateur qui produit d'abord tous les éléments du cache, dans l'ordre d'instanciation, puis localise et instancie paresseusement tous les fournisseurs restants, en les ajoutant chacun au cache à son tour. Le cache peut être effacé via la méthode de rechargement.
Jusqu'ici tout va bien. Cela fait partie de la FactoryFinder#findServiceProvider
méthode OpenJDK :
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) {
...
}
}
Chaque appel aux findServiceProvider
appels ServiceLoader.load
. Cela crée un nouveau ServiceLoader à chaque fois. De cette façon, il semble qu'il n'y ait aucune utilisation du mécanisme de mise en cache de ServiceLoaders. Chaque appel analyse le chemin de classe pour le ServiceProvider demandé.
Ce que j'ai déjà essayé:
- Je sais que vous pouvez définir une propriété système comme
javax.xml.transform.TransformerFactory
pour spécifier une implémentation spécifique. De cette façon, FactoryFinder n'utilise pas le processus ServiceLoader et son super rapide. Malheureusement, c'est une propriété jvm large et affecte d'autres processus java exécutés dans mon jvm. Par exemple, mon application est livrée avec Saxon et devrait utilisercom.saxonica.config.EnterpriseTransformerFactory
J'ai une autre application qui n'est pas livrée avec Saxon. Dès que j'ai défini la propriété système, mon autre application ne démarre pas, car il n'y en a pascom.saxonica.config.EnterpriseTransformerFactory
sur son chemin de classe. Donc, cela ne semble pas être une option pour moi. - J'ai déjà refactorisé chaque endroit où un
TransformerFactory.newInstance
est appelé et mis en cache TransformerFactory. Mais il y a plusieurs endroits dans mes dépendances où je ne peux pas refactoriser le code.
Ma question est la suivante: pourquoi FactoryFinder ne réutilise-t-il pas un ServiceLoader? Existe-t-il un moyen d'accélérer l'ensemble de ce processus ServiceLoader autre que l'utilisation des propriétés système? Cela ne pourrait-il pas être modifié dans le JDK afin qu'un FactoryFinder réutilise une instance de ServiceLoader? De plus, cela n'est pas spécifique à un seul FactoryFinder. Ce comportement est le même pour toutes les classes FactoryFinder dans le javax.xml
package que j'ai examiné jusqu'à présent.
J'utilise OpenJDK 8/11. Mes applications sont déployées dans une instance de Tomcat 9.
Modifier: fournir plus de détails
Voici la pile d'appels pour un seul appel XMLInputFactory.newInstance:
Où la plupart des ressources sont utilisées se trouve ServiceLoaders$LazyIterator.hasNextService
. Cette méthode appelle getResources
ClassLoader pour lire le META-INF/services/javax.xml.stream.XMLInputFactory
fichier. Cet appel à lui seul prend environ 35 ms à chaque fois.
Existe-t-il un moyen de demander à Tomcat de mieux mettre ces fichiers en cache afin qu'ils soient servis plus rapidement?
la source
-D
indicateur sur votreTomcat
processus? Par exemple:-Djavax.xml.transform.TransformerFactory=<factory class>.
il ne doit pas remplacer les propriétés d'autres applications. Votre message est bien décrit et vous l'avez probablement essayé, mais je voudrais le confirmer. Voir Comment définir la propriété système Javax.xml.transform.TransformerFactory , Comment définir les arguments HeapMemory ou JVM dans TomcatRéponses:
35 ms semblent être des temps d'accès au disque impliqués, ce qui indique un problème de mise en cache du système d'exploitation.
S'il y a des entrées de répertoire / non-jar sur le chemin de classe qui peuvent ralentir les choses. Aussi, si la ressource n'est pas présente au premier emplacement vérifié.
ClassLoader.getResource
peut être remplacé si vous pouvez définir le chargeur de classe de contexte de thread, soit par le biais de la configuration (je n'ai pas touché tomcat depuis des années) ou simplementThread.setContextClassLoader
.la source
Je pourrais disposer de 30 minutes supplémentaires pour déboguer cela et voir comment Tomcat effectue la mise en cache des ressources.
En particulier
CachedResource.validateResources
(qui peut être trouvé dans le graphique ci-dessus) était d'un intérêt pour moi. Il retournetrue
si leCachedResource
est toujours valide:On dirait qu'un CachedResource a réellement un temps à vivre (ttl). Il existe en fait un moyen dans Tomcat de configurer le cacheTtl mais vous ne pouvez qu'augmenter cette valeur. La configuration de la mise en cache des ressources n'est pas vraiment flexible, semble-t-il.
Mon Tomcat a donc la valeur par défaut de 5000 ms configurée. Cela m'a piégé lors des tests de performances car j'avais un peu plus de 5 secondes entre mes demandes (en regardant les graphiques et tout ça). C'est pourquoi toutes mes demandes ont essentiellement fonctionné sans cache et ont déclenché ce lourd à
ZipFile.open
chaque fois.Donc, comme je ne suis pas vraiment très expérimenté avec la configuration de Tomcat, je ne suis pas encore sûr de la bonne solution ici. L'augmentation du cacheTTL conserve les caches plus longtemps mais ne résout pas le problème à long terme.
Sommaire
Je pense qu'il y a en fait deux coupables ici.
Les classes FactoryFinder ne réutilisent pas un ServiceLoader. Il pourrait y avoir une raison valable pour laquelle ils ne les réutilisent pas - je ne peux pas vraiment y penser cependant.
Tomcat expulsant les caches après un temps fixe pour la ressource d'application Web (fichiers dans le chemin de classe - comme une
ServiceLoader
configuration)Combinez cela sans avoir défini la propriété système de la classe ServiceLoader et vous obtenez un appel FactoryFinder lent toutes les
cacheTtl
secondes.Pour l'instant, je peux vivre avec l'augmentation de cacheTtl sur une plus longue période. Je pourrais également jeter un coup d'œil à la suggestion de Tom Hawtins de passer outre
Classloader.getResources
même si je pense en quelque sorte que c'est un moyen difficile de se débarrasser de ce goulot d'étranglement des performances. Cela vaut peut-être la peine de regarder cependant.la source