我正在使用第三方库,它动态创建Java类的实例,并在Introspector.getBeanInfo
的帮助下填充这些实例。某些请求可能会导致对Introspector.getBeanInfo
的5或6次连续呼叫。我发现当应用程序空闲大约一个小时左右时,对Introspector.getBeanInfo
的第一次调用需要比后续调用(<100毫秒)执行(20-60秒)更长的时间。在接下来的几分钟内进行的通话继续采取&lt; 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)
(它是Sun的JRE中的私有方法,从查看代码似乎跳过BeanInfo类的查找),我仍然能够重现延迟。
我还搜索了有关任何类型的JRE / JVM jar缓存的信息,但还没有找到任何似乎可以解释这种行为的东西。任何人都有任何线索为什么这样做会如此,如果我有什么办法可以修复它?
作为旁注,我在Windows XP上使用JDK 1.6.0_21。我使用的第三方库是BlazeDS。我的应用程序使用Spring / BlazeDS集成托管在Tomcat中。我覆盖了许多BlazeDS类,以便精确查明延迟的位置(这是Introspector.getBeanInfo
getPropertyDescriptorCacheEntry
方法中对flex.messaging.io.BeanProxy
的调用。此外,BlazeDS会缓存BeanInfo,因此只有在Blaze对映射到尚未处理的Java类的对象进行反序列化时才会调用Introspector.getBeanInfo
。所以,我确实有其他方法可以解决这个问题,但我真的想知道是否有这种行为的有效解释。
编辑: 我在复制问题时多次运行jstack进程(感谢@Tom)并确认它与加载jar文件有关。我在20秒的时间范围内(延迟的总时间)将线程丢弃了5次,每次产生以下结果:
"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 jar缓存在一小时后过期并强制重新扫描jar文件,但我找不到任何在线概述这种行为的东西。
编辑:
事实证明,Tomcat WebappClassLoader
会缓存JAR文件并定期清除该缓存。现在要查明该缓存是否可以配置......
编辑:
在上次访问jar文件90秒后,Tomcat会关闭所有JAR文件。当jar文件关闭时,我覆盖了WebappClassLoader
以打印出来。 jar文件关闭后,我试图重现延迟,但无法。因此,这告诉我有一个JRE / JVM jar文件缓存,或者只是操作系统(或我的机器,防病毒等)中固有的东西,这些东西在长时间延迟后导致加载时间变慢。还在努力......
答案 0 :(得分:3)
在我的雇主,我们也非常依赖动态生成的课程。由于Introspector
的问题及其行为(例如依赖于ClassLoader行为)并尝试加载我们没有的许多*BeanInfo
类,对我们来说这是不行的,我们决定不要使用Introspector并重新实现我们自己的功能。
不确定您真正需要多少BeanInfo,但使用反射和本土属性 - 元数据信息组件可能更容易,您可以更好地控制它。更容易,我还意味着,一旦您在其他应用程序服务器上部署应用程序,您将会安全,这些应用程序服务器具有自己的ClassLoader行为,并且比Tomcat上的行为更加繁重且不可配置。
BeanInfo和相关组件是接口,因此您甚至可能只需要重新实现Introspector本身而不是所有使用它的代码。
答案 1 :(得分:2)
java.beans.Introspector
可能是一个真正的问题,因为搜索不存在的类可能特别昂贵。在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.IGNORE_ALL_BEANINFO
标志时,Introspector在Equinox中应该相对安全。它不是,因为在当前的Oracle JVM实现中,除非指定了Introspector.USE_ALL_BEANINFO,否则不使用BeanInfo缓存。显然,这与javadocs直接冲突,所以我要说这实际上是Introspector实现(或文档)中的一个错误。
答案 2 :(得分:0)
正如我所提到的,Tomcat中的WebAppClassLoader
默认会将JAR文件缓存90秒。在90秒后调用Introspector.getBeanInfo
时,我确认Tomcat正在重新加载JAR文件。几分钟不活动后延迟很小,但仍有延迟。我从来没有确定为什么在一小时不活动后延迟时间太长了。
最终,我的解决方案是覆盖WebAppClassLoader
并无限期地缓存JAR。在我们的场景中,这是完全可以接受的,因为我们将Tomcat包装在我们自己的应用程序中,没有其他Web应用程序共享同一个Tomcat实例,并且我们不允许自动重新加载我们的Web应用程序。如果您正在考虑实施类似的解决方案,请记住这一点。
以下是覆盖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
的调用。我们现在看到的业绩增长相当可观。一小时不活动后,我们会在Introspector.getBeanInfo
的新来电时节省10-60秒。在几分钟不活动后,我们节省了100-200毫秒。