2010-11-19 7 views
5

Ich verwende eine 3rd-Party-Bibliothek, die dynamisch Instanzen von Java-Klassen erstellt und diese Instanzen mit Hilfe von Introspector.getBeanInfo füllt. Bestimmte Anforderungen können zu 5 oder 6 aufeinanderfolgenden Aufrufen an Introspector.getBeanInfo führen. Ich habe herausgefunden, dass, wenn die Anwendung für ungefähr eine Stunde oder so im Leerlauf ist, der erste Anruf zu Introspector.getBeanInfo eine wesentlich viel längere Zeit dauert (20-60 Sekunden) als die nachfolgenden Anrufe (< 100 Millisekunden). Anrufe, die innerhalb der nächsten Minuten gemacht werden, dauern weiterhin 100 Millisekunden, aber wenn ich eine weitere Stunde warte, dauert der erste Anruf erneut 20-60 Sekunden.Leistungsprobleme beim Aufruf von java.beans.Introspector.getBeanInfo nach Inaktivität

In einem Versuch, das Verhalten mit einer einfachen Testanwendung neu zu erstellen, habe ich ähnliches Verhalten gefunden, wenn eine Java-Anwendung selbst für eine Stunde nicht ausgeführt wird. Wenn ich zum Beispiel die folgende Konsolenanwendung verwende, kann es 15 Millisekunden dauern. Wenn ich dann eine Stunde warte und die Anwendung erneut starte, dauert es 20 Sekunden.

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

Ich dachte ursprünglich das Problem auf die Tatsache zurückzuführen sein, dass die Introspector Klasse Versucht Instanzen von Klassen auf Basis von Standard-Namenskonventionen zu erstellen, die in meiner Anwendung nicht existieren (zum Beispiel MyClassBeanInfo), und es war Es dauert sehr lange, die JAR-Dateien zu scannen, um diese Klassen zu finden (meine Java-Anwendung hat etwas mehr als 100 referenzierte JAR-Dateien), aber ich habe Introspector.getBeanInfo(MyClass.class, Object.class, Introspector.IGNORE_ALL_BEANINFO) mit Reflektion aufgerufen (es handelt sich um eine private Methode in Suns JRE) Code scheint das Nachschlagen der BeanInfo-Klassen zu überspringen, und ich konnte die Verzögerung noch reproduzieren.

Ich habe auch nach Informationen über jede Art von JRE/JVM Jar-Cache gesucht, aber habe noch nichts gefunden, was dieses Verhalten zu erklären scheint. Jeder weiß, warum sich das so verhält und ob ich etwas dagegen tun kann.

Als Randnotiz verwende ich JDK 1.6.0_21 unter Windows XP. Die 3rd-Party-Bibliothek, die ich verwende, ist BlazeDS. Meine Anwendung wird in Tomcat mit Spring/BlazeDS-Integration gehostet. Ich überschrieb eine Anzahl von BlazeDS-Klassen, um genau festzustellen, wo die Verzögerung war (das ist der Aufruf an Introspector.getBeanInfo in der getPropertyDescriptorCacheEntry Methode von flex.messaging.io.BeanProxy). Außerdem speichert BlazeDS die BeanInfo, daher werden Aufrufe an Introspector.getBeanInfo nur vorgenommen, wenn Blaze ein Objekt deserialisiert, das einer Java-Klasse zugeordnet ist, die noch verarbeitet werden muss. Daher habe ich andere Möglichkeiten, um dieses Problem zu umgehen, aber ich würde gerne wissen, ob es eine gültige Erklärung für dieses Verhalten gibt.

EDIT: Ich lief jstack auf dem Prozess mehrmals während der Wiedergabe des Problems (danke @ Tom) und hat bestätigt, dass es im Zusammenhang mit dem Laden von JAR-Dateien. Ich warf die Fäden 5-mal über einen 20 Sekunden Zeitrahmen (Gesamtzeit der Verzögerung) und jedes Mal das folgende Ergebnis:

"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) 

ich nicht helfen, aber denke, es ist eine Art von JRE/JVM jar Cache, der nach einer Stunde abläuft und die JAR-Dateien zum erneuten Scannen zwingt, aber ich kann nichts online finden, das dieses Verhalten umreißt.

EDIT: Wie sich herausstellt, speichert der Tomcat WebappClassLoader JAR-Dateien und löscht diesen Cache regelmäßig. Jetzt um herauszufinden, ob dieser Cache konfigurierbar ist in jedem Fall ...

EDIT: Tomcat schließt alle JAR-Dateien 90 Sekunden nach dem letzten Zugriff auf eine JAR-Datei. Ich überschrieb die WebappClassLoader, um auszudrucken, wenn die JAR-Dateien geschlossen wurden. Nachdem die JAR-Dateien geschlossen wurden, versuchte ich die Verzögerung zu reproduzieren, konnte dies aber nicht.Das bedeutet, dass entweder ein JRE/JVM-JAR-Dateicache oder nur etwas, das dem Betriebssystem (oder meinem Computer, Antivirenprogramm usw.) inhärent ist, zu langsamen Ladezeiten nach einer langen Verzögerung führt. Arbeitet noch daran ...

+1

Sie könnten versuchen, 'jstack' oder ähnliches zu verwenden, um zu sehen, wo das Programm hinkommt. Auch jede Netzwerkverbindung beteiligt? –

+0

yourkit Profiler kann Ihren Tag speichern .. in der Sonne schauen Java-Code ist in Ordnung, und Sie können interessante Dinge lernen, aber es dauert viel zu viel Zeit. –

+0

Danke @Tom - Siehe Änderungen nach dem Ausführen von jstack –

Antwort

3

Bei meinem Arbeitgeber verlassen wir uns auch stark auf dynamisch generierte Klassen. Mit den Problemen der Introspector und wie es sich verhält (zB auf ClassLoader-Verhalten angewiesen) und versucht, viele *BeanInfo Klassen zu laden, die wir nicht haben, war es ein No-Go für uns und wir entschieden uns, Introspector nicht zu verwenden und die Funktionalität neu zu implementieren unser eigenes.

Nicht sicher, wie viel von der BeanInfo Sie wirklich brauchen, aber es kann einfacher sein, Reflexion und eine Eigenheim-Metadaten-Informationskomponente zu verwenden, die Sie besser unter Kontrolle haben. Und damit meine ich auch die Nerven, die Sie sicher haben, wenn Sie die App auf anderen Anwendungsservern bereitstellen, die ihr eigenes ClassLoader-Verhalten haben und die noch schwerer und nicht konfigurierbar sind als die auf Tomcat.

BeanInfo und verwandte Komponenten sind Schnittstellen, so dass Sie möglicherweise nur den Introspector selbst neu implementieren müssen und nicht Ihren gesamten Code, der es verwendet.

+0

Danke für die Vorschläge. Leider ist der erste Vorschlag für uns keine Option, da wir Drittanbieterbibliotheken verwenden, die sich auf Introspector.getBeanInfo stützen. Das Überschreiben der Introspector-Klasse könnte der richtige Weg sein. Im Moment möchte ich den Tomcat Classloader außer Kraft setzen (was natürlich nur in Tomcat funktionieren würde, aber für unsere aktuellen Bedürfnisse ausreichend ist). –

0

Wie bereits erwähnt, speichert die WebAppClassLoader in Tomcat standardmäßig JAR-Dateien für 90 Sekunden. Beim Aufruf von Introspector.getBeanInfo nach 90 Sekunden habe ich überprüft, dass Tomcat die JAR-Dateien neu geladen hat. Die Verzögerung war minimal nach ein paar Minuten Inaktivität, aber es gab immer noch eine Verzögerung. Ich habe nie herausgefunden, warum die Verspätung nach einer Stunde Inaktivität so viel länger war.

Letztendlich war meine Lösung, die WebAppClassLoader zu überschreiben und die JARs auf unbestimmte Zeit zwischenzuspeichern. In unserem Szenario ist dies vollkommen akzeptabel, da wir Tomcat in unsere eigene Anwendung einpacken, es keine anderen Webanwendungen gibt, die dieselbe Instanz von Tomcat verwenden, und wir nicht zulassen, dass unsere Webanwendung automatisch neu geladen wird. Denken Sie daran, wenn Sie eine ähnliche Lösung in Betracht ziehen. Hier

ist der Code für das Überschreiben des JAR-Caching-Verhaltens (cacheJarFiles ist eine benutzerdefinierte boolean, die ich die WebAppClassLoader Klasse hinzugefügt habe):

private static boolean cacheJarFiles = true; 

...

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

...

} 

Im Wesentlichen vergebe ich den Anruf zu closeJARs. Der Leistungsanstieg, den wir jetzt beobachten, ist ziemlich beträchtlich. Nach einer Stunde Inaktivität sparen wir 10-60 + Sekunden bei neuen Anrufen an Introspector.getBeanInfo. Nach ein paar Minuten Inaktivität sparen wir 100-200 Millisekunden.

1

java.beans.Introspector kann ein echtes Problem in OSGi-Umgebung sein, wo die Suche nach nicht vorhandenen Klassen besonders teuer sein kann. Dies gilt insbesondere für Equinox, da die Klassenladefähigkeit der Buddy-Klassen Eclipse-BuddyPolicy: depenent und Eclipse-BuddyPolicy: global zu ernsthaften Leistungsproblemen führen kann.

java.beans.Introspector wird in einigen unerwarteten Orten

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

Im Allgemeinen verwendet, sollte relativ sicher sein Introspector in Equinox, wenn Introspector.IGNORE_ALL_BEANINFO Flag angegeben ist. In der aktuellen Oracle JVM-Implementierung wird das BeanInfo-Caching nicht verwendet, es sei denn, Introspector.USE_ALL_BEANINFO wird angegeben. Das ist natürlich in direktem Konflikt mit den Javadocs, also würde ich sagen, dass dies tatsächlich ein Fehler in der Introspector-Implementierung (oder Dokumentation) ist.

+1

Ich habe gesehen, dass, wenn 'Introspector.IGNORE_ALL_BEANINFO' als Parameter angegeben wird, die Klassen' * BeanInfo' nicht mehr nachgeschlagen werden. Es scheint jedoch, dass es keine Möglichkeit gibt, zu verhindern, dass der Algorithmus nach "Customizer" sucht. – Cristian