2010-11-19 9 views
5

J'utilise une bibliothèque tierce qui crée dynamiquement des instances de classes Java et les remplit à l'aide de Introspector.getBeanInfo. Certaines demandes peuvent entraîner 5 ou 6 appels successifs à Introspector.getBeanInfo. J'ai trouvé que lorsque l'application est inactive pendant environ une heure ou deux, le premier appel à Introspector.getBeanInfo prend beaucoup plus de temps à exécuter (20-60 secondes) par rapport aux appels suivants (< 100 millisecondes). Les appels effectués dans les prochaines minutes continuent à prendre < 100 millisecondes, mais quand j'attends encore une heure, le premier appel prend encore 20-60 secondes.Problèmes de performances lors de l'appel de java.beans.Introspector.getBeanInfo après l'inactivité

Dans une tentative de recréer le comportement avec une application de test simple, j'ai trouvé un comportement similaire lorsqu'une application Java elle-même n'est pas exécutée pendant une heure. Par exemple, si j'exécute l'application de console suivante, cela peut prendre 15 millisecondes. Si j'attends une heure et réexécute l'application, il faut 20 secondes pour terminer.

long start = System.currentTimeMillis(); 
System.out.println("Start"); 
Introspector.getBeanInfo(MyClass.class, Object.class); 
long end = System.currentTimeMillis(); 
System.out.println("End: " + (end-start)); 

Je pensais à l'origine du problème peut être lié au fait que les tentatives de classe Introspector pour créer des instances de classes fondées sur les conventions de nommage standard qui n'existent pas dans mon application (par exemple, MyClassBeanInfo), et il était Il faut beaucoup de temps pour analyser les fichiers jar afin de trouver ces classes (mon application java contient un peu plus de 100 fichiers jar référencés), mais j'ai appelé Introspector.getBeanInfo(MyClass.class, Object.class, Introspector.IGNORE_ALL_BEANINFO) en utilisant la réflexion (c'est une méthode privée dans JRE de Sun le code semble ignorer la recherche des classes BeanInfo), et j'étais encore capable de reproduire le délai.

J'ai également recherché des informations concernant n'importe quel type de cache JAR JRE/JVM, mais je n'ai pas encore trouvé quelque chose qui semble expliquer ce comportement. Quelqu'un at-il la moindre idée de la raison pour laquelle cela se comporte ainsi, et s'il y a quelque chose que je peux faire pour le réparer?

En note, j'utilise JDK 1.6.0_21 sous Windows XP. La bibliothèque tierce que j'utilise est BlazeDS. Mon application est hébergée dans Tomcat en utilisant l'intégration Spring/BlazeDS. J'ai écrasé un certain nombre de classes BlazeDS afin de localiser exactement où le retard était (qui est l'appel à Introspector.getBeanInfo dans la méthode getPropertyDescriptorCacheEntry de flex.messaging.io.BeanProxy). En outre, BlazeDS met en cache le BeanInfo, donc les appels à Introspector.getBeanInfo ne sont effectués que lorsque Blaze désérialise un objet mappé à une classe Java qui n'a pas encore été traitée. Donc, j'ai d'autres façons de contourner ce problème, mais j'aimerais vraiment savoir s'il existe une explication valable pour ce comportement. J'ai exécuté jstack sur le processus plusieurs fois tout en reproduisant le problème (merci @Tom) et ai confirmé qu'il est lié au chargement des fichiers jar. Je jetai les fils 5 fois sur un châssis 20 deuxième fois (durée totale du retard) et à chaque fois produit le résultat suivant:

"http-8080-exec-6" daemon prio=6 tid=0x65cae800 nid=0x1a50 runnable [0x67a3d000] 
    java.lang.Thread.State: RUNNABLE 
    at java.util.zip.ZipFile.open(Native Method) 
    at java.util.zip.ZipFile.<init>(Unknown Source) 
    at java.util.jar.JarFile.<init>(Unknown Source) 
    at java.util.jar.JarFile.<init>(Unknown Source) 
    at org.apache.catalina.loader.WebappClassLoader.openJARs(WebappClassLoader.java:2704) 
    at org.apache.catalina.loader.WebappClassLoader.findResourceInternal(WebappClassLoader.java:2945) 
    - locked <0x1804cc18> (a [Ljava.util.jar.JarFile;) 
    at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2739) 
    at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1144) 
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1639) 
    - locked <0x1803dd38> (a org.apache.catalina.loader.WebappClassLoader) 
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1517) 
    at java.beans.Introspector.instantiate(Unknown Source) 
    at java.beans.Introspector.findExplicitBeanInfo(Unknown Source) 
    - locked <0x434649a0> (a java.lang.Class for java.beans.Introspector) 
    at java.beans.Introspector.<init>(Unknown Source) 
    at java.beans.Introspector.getBeanInfo(Unknown Source) 
    - locked <0x181bed70> (a java.lang.Object) 

Je ne peux pas empêcher de penser qu'il ya une sorte de pot JRE/JVM cache qui expire après une heure et forçant les fichiers jar à être re-scannés, mais je ne trouve rien en ligne qui décrit un tel comportement. Comme il s'avère que le Tomcat WebappClassLoader cache les fichiers JAR et purge périodiquement ce cache. Maintenant, pour savoir si ce cache est configurable de toute façon ...

EDIT: Tomcat ferme tous les fichiers JAR 90 secondes après le dernier accès à un fichier jar. J'ai écrasé WebappClassLoader pour imprimer lorsque les fichiers jar ont été fermés. Après la fermeture des fichiers jar, j'ai essayé de reproduire le délai, mais je n'ai pas pu le faire.Donc, cela me dit qu'il y a un cache de fichier JAR JRE/JVM, ou juste quelque chose d'inhérent au système d'exploitation (ou ma machine, antivirus, etc) qui provoque des temps de chargement lent après un long délai. Toujours en train de travailler ...

+1

Vous pourriez essayer d'utiliser 'jstack' ou similaire pour voir où le programme est arrivé. En outre, toute connexion réseau impliqué? –

+0

Votre profileur profil peut sauver votre journée ... regarder dans le code java du soleil est très bien et vous pouvez apprendre des choses intéressantes, mais cela prend beaucoup trop de temps. –

+0

Merci @Tom - Voir les modifications ci-dessus après l'exécution jstack –

Répondre

3

Chez mon employeur, nous dépendons aussi beaucoup des classes générées dynamiquement. Avec les problèmes du Introspector et comment il se comporte (par exemple en s'appuyant sur le comportement ClassLoader) et en essayant de charger beaucoup de classes *BeanInfo que nous n'avons pas, c'était un non-go pour nous et nous avons décidé de ne pas utiliser Introspector et réimplémenter la fonctionnalité sur notre propre.

Vous ne savez pas quelle quantité de BeanInfo vous avez vraiment besoin, mais il est peut-être plus facile d'utiliser la réflexion et un composant de propriété-métadonnées-information développé localement, que vous maîtrisez mieux. Et avec plus de facilité, je veux dire aussi que vous serez en sécurité une fois que vous déploierez l'application sur d'autres serveurs d'applications, qui ont leurs propres comportements ClassLoader et qui sont encore plus lourds et non configurables que sur Tomcat. BeanInfo et les composants associés sont des interfaces, il se peut donc que vous ayez seulement besoin de réimplémenter l'Introspector lui-même et pas tout votre code qui l'utilise.

+0

Merci pour les suggestions. Malheureusement, la première suggestion n'est pas une option pour nous car nous utilisons des bibliothèques tierces qui s'appuient sur Introspector.getBeanInfo. Surpasser la classe Introspector pourrait être la voie à suivre. En ce moment je penche pour surcharger le classloader Tomcat (qui bien sûr ne fonctionnerait que dans Tomcat, mais est suffisant pour nos besoins actuels). –

0

Comme je l'ai mentionné, le WebAppClassLoader dans Tomcat par défaut met en cache les fichiers JAR pendant 90 secondes. Lorsque j'ai appelé Introspector.getBeanInfo après 90 secondes, j'ai vérifié que Tomcat rechargeait les fichiers JAR. Le délai était minime après quelques minutes d'inactivité, mais il y avait encore un retard. Je n'ai jamais déterminé pourquoi le retard était tellement plus long après une heure d'inactivité.

En fin de compte, ma solution était de remplacer le WebAppClassLoader et de mettre en cache les fichiers JAR indéfiniment. Dans notre scénario, cela est parfaitement acceptable car nous enveloppons Tomcat dans notre propre application, il n'y a pas d'autres applications web qui partagent la même instance de Tomcat, et nous ne permettons pas que notre application web soit automatiquement rechargée. Gardez cela à l'esprit si vous envisagez de mettre en œuvre une solution similaire.

Voici le code pour des raisons impérieuses le comportement de mise en cache JAR (cacheJarFiles est un booléen personnalisé que je l'ai ajouté à la classe WebAppClassLoader):

private static boolean cacheJarFiles = true; 

...

public void closeJARs(boolean force) { 
    if (cacheJarFiles) { 
     return; 
    } 
    if (jarFiles.length > 0) { 
     synchronized (jarFiles) { 
     if (force || (System.currentTimeMillis() 

...

} 

Essentiellement, j'annule l'appel à closeJARs. L'augmentation de performance que nous voyons maintenant est assez substantielle. Après une heure d'inactivité, nous économisons 10-60 + secondes sur les nouveaux appels à Introspector.getBeanInfo. Après quelques minutes d'inactivité, nous économisons 100-200 millisecondes.

1

java.beans.Introspectorjava.beans.Introspector peut être un réel problème dans l'environnement OSGi où la recherche de classes non existantes peut être particulièrement coûteux. Cela est particulièrement vrai dans Equinox en raison du chargement de la classe buddy: Eclipse-BuddyPolicy: depenent et Eclipse-BuddyPolicy: global peuvent causer de sérieux problèmes de performance.

java.beans.Introspector est utilisé dans quelques endroits inattendus

  • org.apache.log4j.config.PropertySetter
  • org.springframework.beans.CachedIntrospectionResults
  • org.hibernate.util.Cloneable#copyListeners

En général, Introspector devrait être relativement en sécurité dans Equinox lorsque Introspector.IGNORE_ALL_BEANINFO drapeau est spécifié. Ce n'est pas le cas, car la mise en cache BeanInfo de l'implémentation JVM Oracle actuelle n'est pas utilisée, sauf si Introspector.USE_ALL_BEANINFO est spécifié. C'est, évidemment, en conflit direct avec les javadocs, donc je dirais que c'est en fait un bug dans l'implémentation d'Introspector (ou de la documentation).

+1

J'ai vu que si 'Introspector.IGNORE_ALL_BEANINFO' est donné en paramètre, les classes' * BeanInfo' ne sont plus recherchées. Cependant, il semble qu'il n'y ait aucun moyen d'empêcher l'algorithme de rechercher '* Customizer'. – Cristian

Questions connexes