Проблемы с производительностью при вызове java.beans.Introspector.getBeanInfo после бездействия - PullRequest
5 голосов
/ 19 ноября 2010

Я использую стороннюю библиотеку, которая динамически создает экземпляры классов Java и заполняет эти экземпляры с помощью Introspector.getBeanInfo.Некоторые запросы могут привести к 5 или 6 последовательным вызовам на Introspector.getBeanInfo.Я обнаружил, что когда приложение бездействует около часа или около того, первый вызов Introspector.getBeanInfo занимает значительно более длительное время (20-60 секунд) по сравнению с последующими вызовами (<100 миллисекунд).Вызовы, сделанные в течение следующих нескольких минут, продолжают занимать <100 миллисекунд, но когда я жду еще час, первый вызов снова занимает 20-60 секунд. </p>

В попытке воссоздать поведение с помощью простого тестового приложенияЯ обнаружил подобное поведение, когда само приложение Java не запускается в течение часа.Например, если я запускаю следующее консольное приложение, это может занять 15 миллисекунд.Затем, если я подожду час и перезапущу приложение, его выполнение займет 20 секунд.

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

Я изначально думал, что проблема может быть связана с тем, что класс Introspector пытается создать экземпляры классов на основена стандартных соглашениях об именах, которые не существуют в моем приложении (например, MyClassBeanInfo), и сканирование файлов jar занимало много времени в попытке найти эти классы (мое приложение java имеет чуть более 100 ссылок на jarфайлы), но я вызвал Introspector.getBeanInfo(MyClass.class, Object.class, Introspector.IGNORE_ALL_BEANINFO) с использованием отражения (это частный метод в JRE Sun, который при просмотре кода, похоже, пропускает поиск классов BeanInfo), и я все еще смог воспроизвести задержку.

Я также искал информацию, касающуюся любого типа JAR / JVM JAR-кэша, но пока не нашел ничего, что могло бы объяснить это поведение.Кто-нибудь может понять, почему это ведет себя так, как есть, и если я могу что-то сделать, чтобы это исправить?

В качестве примечания я использую JDK 1.6.0_21 в Windows XP.Сторонняя библиотека, которую я использую - BlazeDS.Мое приложение размещено в Tomcat с использованием интеграции Spring / BlazeDS.Я переписал несколько классов BlazeDS, чтобы точно определить, где именно была задержка (это вызов Introspector.getBeanInfo в методе getPropertyDescriptorCacheEntry flex.messaging.io.BeanProxy).Кроме того, BlazeDS кэширует BeanInfo, поэтому вызовы Introspector.getBeanInfo выполняются только тогда, когда Blaze десериализует объект, который сопоставлен с классом Java, который еще не обработан.Итак, у меня есть другие способы обойти эту проблему, но я действительно хотел бы знать, если есть правильное объяснение этого поведения.

РЕДАКТИРОВАТЬ: я запускал jstack в процессе несколько раз при воспроизведении проблемы(спасибо @Tom) и подтвердил, что это связано с загрузкой файлов JAR.Я сбрасывал потоки 5 раз за 20 секунд (общее время задержки) и каждый раз получал следующий результат:

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

Не могу не подумать, что существует какая-то JRE /JVM-кэш JARM, срок действия которого истекает через час и заставляет повторно сканировать JAR-файлы, но я не могу найти в Интернете ничего такого, что описывает такое поведение.кэширует JAR-файлы и периодически удаляет этот кеш.Теперь, чтобы узнать, настраивается ли этот кеш в любом случае ...

РЕДАКТИРОВАТЬ: Tomcat закрывает все файлы JAR через 90 секунд после последнего обращения к файлу JAR.Я переписал WebappClassLoader, чтобы распечатать, когда файлы JAR были закрыты.После того, как файлы jar были закрыты, я попытался воспроизвести задержку, но не смог.Итак, это говорит мне о том, что существует либо кэш-файл JAR / JVM jar, либо просто что-то присущее операционной системе (или моей машине, антивирусу и т. Д.), Что вызывает длительную загрузку после длительной задержки.Все еще работаем над этим ...

Ответы [ 3 ]

3 голосов
/ 19 ноября 2010

У моего работодателя мы тоже очень полагаемся на динамически генерируемые классы.Из-за проблем с Introspector и его поведения (например, в зависимости от поведения ClassLoader) и попытки загрузить многие классы *BeanInfo, которых у нас нет, это было бесполезным для нас, и мы решили не использовать Introspector ипереопределяем функциональность сами по себе.

Не уверен, какая часть BeanInfo вам действительно нужна, но может быть проще использовать отражение и доморощенный компонент свойств-метаданных-информации, который вы лучше контролируете,И, говоря проще, я также имею в виду нервы, которые вы обезопасите после развертывания приложения на других серверах приложений, которые имеют свои собственные поведения ClassLoader и которые даже более тяжелы и неконфигурируемы, чем на Tomcat.

BeanInfo исвязанные компоненты - это интерфейсы, поэтому вам может понадобиться лишь переопределить сам Introspector, а не весь код, который его использует.

2 голосов
/ 04 сентября 2013

java.beans.Introspector может быть реальной проблемой в среде OSGi, где поиск несуществующих классов может быть особенно дорогим. Это особенно верно в Equinox из-за загрузки класса друзей: Eclipse-BuddyPolicy: depenent и Eclipse-BuddyPolicy: global могут вызвать серьезные проблемы с производительностью.

java.beans.Introspector используется в нескольких неожиданных местах

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

В общем случае Introspector должен быть относительно безопасным в Equinox, если указан флаг Introspector.IGNORE_ALL_BEANINFO. Это не так, поскольку в текущей реализации Oracle JVM кэширование BeanInfo не используется, если не указан Introspector.USE_ALL_BEANINFO. Это, очевидно, находится в прямом конфликте с javadocs, поэтому я бы сказал, что это на самом деле ошибка в реализации Introspector (или документации).

0 голосов
/ 29 ноября 2010

Как я уже говорил, WebAppClassLoader в Tomcat по умолчанию кэширует JAR-файлы в течение 90 секунд.При вызове Introspector.getBeanInfo через 90 секунд я убедился, что Tomcat перезагружает файлы JAR.Задержка была минимальной после нескольких минут бездействия, но задержка все еще была.Я так и не определил, почему задержка была намного дольше после часа бездействия.

В конечном счете, мое решение состояло в том, чтобы переопределить WebAppClassLoader и кэшировать JAR на неопределенный срок.В нашем сценарии это вполне приемлемо, потому что мы упаковываем Tomcat в наше собственное приложение, нет других веб-приложений, использующих тот же экземпляр Tomcat, и мы не разрешаем автоматически загружать наше веб-приложение.Имейте это в виду, если вы планируете реализовать подобное решение.

Вот код для переопределения поведения кэширования JAR (cacheJarFiles - это пользовательский логический тип, который я добавил в класс WebAppClassLoader):

private static boolean cacheJarFiles = true;

...

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

...

}

По сути, я прерываю вызов на closeJARs.Увеличение производительности, которое мы сейчас наблюдаем, довольно существенное.После часа бездействия мы экономим 10-60 + секунд на новые звонки до Introspector.getBeanInfo.После нескольких минут бездействия мы экономим 100-200 миллисекунд.

...