【问题标题】:Performance issues when calling java.beans.Introspector.getBeanInfo after inactivity不活动后调用 java.beans.Introspector.getBeanInfo 时的性能问题
【发布时间】:2010-11-19 05:55:09
【问题描述】:

我正在使用第 3 方库,它可以动态创建 Java 类的实例并在 Introspector.getBeanInfo 的帮助下填充这些实例。某些请求可能会导致 5 或 6 次连续调用 Introspector.getBeanInfo。我发现当应用程序空闲大约一个小时左右时,对Introspector.getBeanInfo 的第一次调用与后续调用(

在尝试使用简单的测试应用程序重新创建该行为时,我发现当 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。我使用的第 3 方库是 BlazeDS。我的应用程序使用 Spring/BlazeDS 集成托管在 Tomcat 中。我重写了许多 BlazeDS 类,以便准确指出延迟的位置(这是在 getPropertyDescriptorCacheEntryflex.messaging.io.BeanProxy 方法中对 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 文件关闭时,我覆盖了 WebappClassLoader 以打印出来。 jar 文件关闭后,我尝试重现延迟,但无法重现。所以,这告诉我,要么存在 JRE/JVM jar 文件缓存,要么只是操作系统(或我的机器、防病毒软件等)中固有的东西在长时间延迟后导致加载时间变慢。还在努力中……

【问题讨论】:

  • 您可以尝试使用jstack 或类似的方法来查看程序的位置。另外,是否涉及任何网络连接?
  • yourkit profiler 可以为您节省一天的时间。在阳光下看 java 代码很好,您可以学习有趣的东西,但需要太多时间。
  • 感谢@Tom - 运行 jstack 后查看上面的编辑

标签: java performance reflection blazeds javabeans


【解决方案1】:

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

不确定您真正需要多少 BeanInfo,但使用反射和自制的属性元数据信息组件可能更容易,您可以更好地控制它。更简单的意思是,一旦将应用程序部署到其他应用程序服务器上,您就会放心,这些应用程序服务器具有自己的 ClassLoader 行为,并且比 Tomcat 上的更繁重且不可配置。

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

【讨论】:

  • 感谢您的建议。不幸的是,第一个建议对我们来说不是一个选项,因为我们正在使用依赖于 Introspector.getBeanInfo 的 3rd 方库。重写 Introspector 类可能是要走的路。现在我倾向于重写 Tomcat 类加载器(当然它只能在 Tomcat 中工作,但足以满足我们当前的需求)。
【解决方案2】:

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 在 Equinox 中应该是相对安全的。不是,因为在当前的 Oracle JVM 实现中不使用 BeanInfo 缓存,除非指定了 Introspector.USE_ALL_BEANINFO。很明显,这与 javadocs 直接冲突,所以我想说这实际上是 Introspector 实现(或文档)中的一个错误。

【讨论】:

  • 我看到如果Introspector.IGNORE_ALL_BEANINFO 作为参数,*BeanInfo 类就不再被查找了。但是,似乎没有办法阻止算法查找*Customizer
【解决方案3】:

正如我所提到的,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 毫秒。

【讨论】:

    猜你喜欢
    • 2020-02-01
    • 1970-01-01
    • 2014-07-16
    • 2013-01-29
    • 1970-01-01
    • 2011-10-20
    • 2017-07-10
    • 1970-01-01
    • 2012-04-18
    相关资源
    最近更新 更多