2010-11-19 11 views
5

Używam biblioteki innej firmy, która dynamicznie tworzy wystąpienia klas Java i wypełnia te instancje za pomocą Introspector.getBeanInfo. Niektóre żądania mogą skutkować 5 lub 6 kolejnymi połączeniami do Introspector.getBeanInfo. Zauważyłem, że gdy aplikacja jest bezczynna przez około godzinę, pierwsze wywołanie Introspector.getBeanInfo wymaga znacznie dłuższego czasu do wykonania (20-60 sekund) w porównaniu z kolejnymi połączeniami (< 100 milisekund). Połączenia wykonane w ciągu kilku następnych minut trwają przez cały czas <, ale kiedy poczekam na kolejną godzinę, pierwsze połączenie ponownie zajmie 20-60 sekund.Problemy z wydajnością podczas wywoływania java.beans.Introspector.getBeanInfo po nieaktywności

Próbując odtworzyć zachowanie za pomocą prostej aplikacji testowej, znalazłem podobne zachowanie, gdy sama aplikacja java nie jest uruchamiana przez godzinę. Na przykład, jeśli uruchomię poniższą aplikację konsoli, ukończenie jej może potrwać 15 milisekund. Jeśli następnie poczekam godzinę i ponownie uruchomę aplikację, jej ukończenie zajmie 20 sekund.

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

I początkowo myślałem że problem może być związany z faktem, że próby klasy Introspector tworzyć instancje klas opartych na standardowych konwencji nazewnictwa, które nie istnieją w mojej aplikacji (na przykład MyClassBeanInfo), i było długo trwa skanowanie plików jar w celu znalezienia tych klas (moja aplikacja java ma niewiele ponad 100 odwołanych plików jar), ale nazwałem je Introspector.getBeanInfo(MyClass.class, Object.class, Introspector.IGNORE_ALL_BEANINFO) za pomocą refleksji (jest to prywatna metoda w JRE firmy Sun, która od spojrzenia na kod wydaje się pomijać klasy BeanInfo), a ja wciąż mogłem odtworzyć opóźnienie.

Też szukałem informacji dotyczących każdego typu bufora JRE/JVM, ale jeszcze nie znalazłem niczego, co wydaje się wyjaśniać to zachowanie. Ktoś ma jakąś wskazówkę, dlaczego tak się zachowuje, i czy jest coś, co mogę zrobić, żeby to naprawić?

Na marginesie używam JDK 1.6.0_21 w systemie Windows XP. Używam biblioteki firm zewnętrznych BlazeDS. Moja aplikacja jest hostowana w Tomcat z wykorzystaniem integracji Spring/BlazeDS. Nadpisałem kilka klas BlazeDS, aby dokładnie określić, gdzie było opóźnienie (to jest połączenie z Introspector.getBeanInfo w metodzie getPropertyDescriptorCacheEntry z flex.messaging.io.BeanProxy). BlazeDS buforuje również BeanInfo, więc wywołania do Introspector.getBeanInfo są tworzone tylko wtedy, gdy Blaze deserializuje obiekt, który jest odwzorowany na klasę Java, która nie została jeszcze przetworzona. Tak, mam inne sposoby obejścia tego problemu, ale naprawdę chciałbym wiedzieć, czy istnieje uzasadnione wyjaśnienie tego zachowania.

EDYCJA: Uruchomiłem jstack w procesie wiele razy podczas odtwarzania problemu (dzięki @Tom) i potwierdziłem, że jest on związany z ładowaniem plików jar. Rzuciłam nitek 5 razy w ciągu 20 sekund przedziale czasowym (całkowity czas opóźnienia) i za każdym razem produkowane następujący wynik:

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

nie mogę pomóc, ale myślę, że jest jakiś rodzaj JRE/JVM słoiku pamięć podręczna wygasająca po godzinie i wymuszająca ponowne skanowanie plików JAR, ale nie mogę znaleźć niczego w Internecie, które nakreśla takie zachowanie.

EDYTOWANIE: Okazuje się, że Tomcat WebappClassLoader buforuje pliki JAR i okresowo usuwa pamięć podręczną. Teraz, aby dowiedzieć się, czy pamięć podręczna jest konfigurowalna w każdym razie ...

EDYTOWANIE: Tomcat zamyka wszystkie pliki JAR 90 sekund po ostatnim dostępie do pliku jar. Nadpisałem numer WebappClassLoader, aby wydrukować, gdy pliki JAR zostały zamknięte. Po zamknięciu plików JAR próbowałem odtworzyć opóźnienie, ale nie mogłem.Mówi mi to, że istnieje albo pamięć podręczna plików JRE/JVM, albo tylko coś związanego z systemem operacyjnym (lub moją maszyną, programem antywirusowym itp.), Które powoduje powolne czasy ładowania po długim opóźnieniu. Nadal nad nim pracuję ...

+1

Możesz spróbować użyć 'jstack' lub podobnego, aby zobaczyć, gdzie program się dostał. Ponadto, jakiekolwiek połączenie sieciowe jest zaangażowane? –

+0

profiler yourkit może uratować twój dzień .. patrząc na słońce kod java jest w porządku i możesz uczyć się ciekawych rzeczy, ale zajmuje to zbyt dużo czasu. –

+0

Dzięki @Tom - Zobacz zmiany powyżej po uruchomieniu jstack –

Odpowiedz

3

W moim pracodawcy, my także mocno polegamy na dynamicznie generowanych klasach. Z problemami z Introspector i ich zachowaniem (np. Polegając na zachowaniu ClassLoader) i próbach załadowania wielu klas *BeanInfo, których nie mamy, nie było dla nas miejsca i zdecydowaliśmy się nie używać Introspectora i ponownie wdrożyć funkcjonalność nasz własny.

Nie jestem pewien, ile naprawdę potrzebujesz BeanInfo, ale może być łatwiej użyć refleksji i komponentu informacji o nieruchomościach z metadanymi, które masz lepiej pod kontrolą. I łatwiej, mam na myśli także nerwy, które będziesz bezpieczny po wdrożeniu aplikacji na innych serwerach aplikacji, które mają własne zachowania ClassLoader i które są jeszcze cięższe i nie konfigurowalne niż te na Tomcat.

BeanInfo i powiązane komponenty to interfejsy, więc możesz potrzebować tylko do ponownego zaimplementowania samego introspektora, a nie całego kodu, który z niego korzysta.

+0

Dzięki za sugestie. Niestety, pierwsza sugestia nie jest dla nas opcją, ponieważ korzystamy z bibliotek stron trzecich, które bazują na Introspector.getBeanInfo. Zastąpienie klasy Introspector może być drogą do zrobienia. W tej chwili jestem skłonny do nadrobienia programu ładującego klasy Tomcat (który oczywiście działałby tylko w Tomcat, ale jest wystarczający dla naszych aktualnych potrzeb). –

0

Jak już wspomniałem, WebAppClassLoader w Tomcat domyślnie buforuje pliki JAR przez 90 sekund. Dzwoniąc pod numer Introspector.getBeanInfo po 90 sekundach, sprawdziłem, czy Tomcat ponownie załadował pliki JAR. Opóźnienie było minimalne po kilku minutach bezczynności, ale wciąż było opóźnienie. Nigdy nie ustaliłem, dlaczego opóźnienie było znacznie dłuższe po godzinie bezczynności.

Ostatecznie moim rozwiązaniem było zastąpienie WebAppClassLoader i buforowanie plików JAR w nieskończoność. W naszym scenariuszu jest to całkowicie dopuszczalne, ponieważ pakujemy Tomcat w naszą własną aplikację, nie ma innych aplikacji internetowych, które dzielą tę samą instancję Tomcat, i nie zezwalamy na automatyczne ponowne ładowanie naszej aplikacji internetowej. Miej to na uwadze, jeśli rozważasz wdrożenie podobnego rozwiązania.

Oto kod nadrzędne zachowanie buforowania JAR (cacheJarFiles jest logiczna zwyczaj, że dodałem do klasy WebAppClassLoader):

private static boolean cacheJarFiles = true; 

...

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

...

} 

Zasadniczo przerywam połączenie z closeJARs. Wzrost wydajności, który obecnie obserwujemy, jest dość znaczny. Po godzinie bezczynności oszczędzamy 10-60 + sekund na nowych połączeniach z Introspector.getBeanInfo. Po kilku minutach bezczynności oszczędzamy 100-200 milisekund.

1

java.beans.Introspector może być prawdziwym problemem w środowisku OSGi, w którym poszukiwanie nieistniejących klas może być szczególnie kosztowne. Jest to szczególnie prawdziwe w przypadku równonocy ze względu na ładowanie klasy znajomych: Eclipse-BuddyPolicy: depenent i Eclipse-BuddyPolicy: global może powodować poważne problemy z wydajnością.

java.beans.Introspector stosowany jest w kilku miejscach nieoczekiwanych

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

Ogólnie Introspector powinny być stosunkowo bezpieczne w Equinox gdy Introspector.IGNORE_ALL_BEANINFO flaga jest określona. Nie jest, ponieważ w obecnej implementacji Oracle JVM buforowanie BeanInfo nie jest używane, chyba że podany jest Introspector.USE_ALL_BEANINFO. To jest oczywiście w bezpośrednim konflikcie z javadocsami, więc powiedziałbym, że jest to błąd w implementacji Introspectora (lub dokumentacji).

+1

Widziałem, że jeśli parametr 'Introspector.IGNORE_ALL_BEANINFO' jest podany jako parametr, to klasy' * BeanInfo' nie są już wyszukiwane. Jednak wydaje się, że nie ma sposobu, aby uniemożliwić algorytmowi wyszukiwanie "* Customizer". – Cristian

Powiązane problemy