繁体   English   中英

不活动后调用java.beans.Introspector.getBeanInfo时的性能问题

[英]Performance issues when calling java.beans.Introspector.getBeanInfo after inactivity

我正在使用第3方库,该库动态创建Java类的实例,并在Introspector.getBeanInfo的帮助下填充这些实例。 某些请求可能导致对Introspector.getBeanInfo 5或6个连续调用。 我发现,当应用程序闲置约一个小时左右时,首次调用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)在Sun的JRE中,通过查看代码似乎可以跳过对BeanInfo类的查找),而我仍然能够重现该延迟。

我还搜索了有关任何类型的JRE / JVM jar缓存的信息,但尚未找到任何似乎可以解释这种现象的信息。 任何人都有线索为什么这样做会如此,如果我有什么办法可以解决它?

附带说明一下,我在Windows XP上使用JDK 1.6.0_21。 我正在使用的第三方图书馆是BlazeDS。 我的应用程序使用Spring / BlazeDS集成托管在Tomcat中。 我改写了许多BlazeDS类,以便准确确定延迟的位置(这是对flex.messaging.io.BeanProxygetPropertyDescriptorCacheEntry方法中的Introspector.getBeanInfo的调用)。 另外,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文件并定期清除该缓存。 现在,找出该缓存是否仍可配置...

编辑:Tomcat在上次访问jar文件后90秒关闭了所有JAR文件。 关闭jar文件时,我WebappClassLoaderWebappClassLoader以进行打印。 关闭jar文件后,我尝试重现延迟,但无法恢复。 因此,这告诉我要么是JRE / JVM jar文件缓存,要么是操作系统(或我的机器,防病毒等)中固有的东西,这些东西导致长时间的延迟后导致加载时间变慢。 仍在努力中...

在OSGi环境中, java.beans.Introspector可能是一个真正的问题,在OSGi环境中,搜索不存在的类可能特别昂贵。 由于伙伴类的加载,在Equinox中尤其如此: Eclipse-BuddyPolicy: depenentEclipse-BuddyPolicy: global会导致严重的性能问题。

java.beans.Introspector用于少数意外的地方

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

通常,当指定Introspector.IGNORE_ALL_BEANINFO标志时, Introspector.IGNORE_ALL_BEANINFO在Equinox中应该相对安全。 不会,因为在当前的Oracle JVM实现中,除非指定了Introspector.USE_ALL_BEANINFO,否则不使用BeanInfo缓存。 显然,这与javadocs直接冲突,因此我想说这实际上是Introspector实现(或文档)中的错误。

在我的雇主处,我们也严重依赖动态生成的类。 考虑到Introspector的问题及其行为方式(例如,依赖ClassLoader行为),并尝试加载许多我们没有的*BeanInfo类,这对我们来说是不行的,我们决定不使用Introspector并重新实现功能靠我们自己。

不确定您真正需要多少BeanInfo,但是使用反射和自定义属性-元数据信息组件可能会更容易,您可以更好地控制它们。 而且,更容易地说,我还表示,一旦将应用程序部署到其他应用程序服务器上就可以安心,它们具有自己的ClassLoader行为,并且比Tomcat上的行为更沉重且不可配置。

BeanInfo和相关组件是接口,因此您甚至只需要重新实现Introspector本身,而不是重新使用它的所有代码。

如前所述,默认情况下,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毫秒。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM