Проблемы с производительностью при вызове java.beans.Introspector.getBeanInfo после бездействия
Я использую стороннюю библиотеку, которая динамически создает экземпляры классов Java и заполняет эти экземпляры с помощью Introspector.getBeanInfo
, Некоторые запросы могут привести к 5 или 6 последовательным вызовам Introspector.getBeanInfo
, Я обнаружил, что когда приложение бездействует около часа, первый звонок Introspector.getBeanInfo
выполнение значительно дольше (20-60 секунд) по сравнению с последующими вызовами (< 100 миллисекунд). Вызовы, сделанные в течение следующих нескольких минут, продолжают занимать <100 миллисекунд, но когда я жду еще час, первый звонок снова занимает 20-60 секунд.
В попытке воссоздать поведение с помощью простого тестового приложения я обнаружил аналогичное поведение, когда само приложение 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)
Я не могу не думать, что существует какой-то JAR / JVM-кэш JAR, срок действия которого истекает через час и заставляет повторно сканировать файлы JAR, но я не могу найти в Интернете ничего такого, что описывает такое поведение.
РЕДАКТИРОВАТЬ: Как выясняется, Tomcat WebappClassLoader
кэширует JAR-файлы и периодически удаляет этот кеш. Теперь, чтобы узнать, настраивается ли этот кеш в любом случае...
РЕДАКТИРОВАТЬ: Tomcat закрывает все файлы JAR через 90 секунд после последнего обращения к файлу JAR. Я переписал WebappClassLoader
распечатать, когда файлы jar были закрыты. После того, как файлы были закрыты, я попытался воспроизвести задержку, но не смог. Итак, это говорит мне о том, что существует либо кэш-файл JAR /JVM jar, либо просто что-то присуще операционной системе (или моей машине, антивирусу и т. Д.), Которая вызывает длительную загрузку после большой задержки. Все еще работаю над этим...
3 ответа
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 должен быть относительно безопасным в Равноденствие, когда Introspector.IGNORE_ALL_BEANINFO
флаг указан. Это не так, поскольку в текущей реализации Oracle JVM кэширование BeanInfo не используется, если не указан Introspector.USE_ALL_BEANINFO. Это, очевидно, находится в прямом конфликте с javadocs, поэтому я бы сказал, что это на самом деле ошибка в реализации Introspector (или документации).
У моего работодателя мы тоже сильно полагаемся на динамически генерируемые классы. С проблемами Introspector
и как он себя ведет (например, полагается на поведение ClassLoader) и пытается загрузить много *BeanInfo
классы, которых у нас нет, для нас это было бесполезно, и мы решили не использовать Introspector и заново реализовать функциональность самостоятельно.
Не уверен, какая часть BeanInfo вам действительно нужна, но может быть проще использовать рефлексию и собственный компонент свойства-метаданные-информация, который вы лучше контролируете. И с более легким, я также имею в виду нервы, которые вы будете в безопасности, когда вы развернете приложение на других серверах приложений, которые имеют свои собственные поведения ClassLoader и которые даже более тяжелы и неконфигурируемы, чем на Tomcat.
BeanInfo и связанные компоненты являются интерфейсами, поэтому вам может понадобиться лишь переопределить сам Introspector, а не весь код, который его использует.
Как я уже говорил, 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 миллисекунд.