FactoryFinder性能/不良缓存


9

我有一个相当大的Java ee应用程序,它具有执行许多xml处理的巨大类路径。目前,我正在尝试加快某些功能的速度,并通过采样分析器定位慢速代码路径。

我注意到的一件事是,特别是我们在其中调用类似代码的部分TransformerFactory.newInstance(...)非常慢。我跟踪下来到FactoryFinder方法findServiceProvider始终创建一个新的ServiceLoader实例。在ServiceLoader javadoc中,我发现了有关缓存的以下说明:

提供商的位置很懒,即按需实例化。服务加载器维护到目前为止已加载的提供者的缓存。每次迭代器方法的调用都会返回一个迭代器,该迭代器首先按实例化顺序生成高速缓存的所有元素,然后懒惰地定位和实例化任何剩余的提供程序,依次将每个提供程序添加到高速缓存中。可以通过reload方法清除缓存。

到目前为止,一切都很好。这是OpenJDKs FactoryFinder#findServiceProvider方法的一部分:

private static <T> T findServiceProvider(final Class<T> type)
        throws TransformerFactoryConfigurationError
    {
      try {
            return AccessController.doPrivileged(new PrivilegedAction<T>() {
                public T run() {
                    final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
                    final Iterator<T> iterator = serviceLoader.iterator();
                    if (iterator.hasNext()) {
                        return iterator.next();
                    } else {
                        return null;
                    }
                 }
            });
        } catch(ServiceConfigurationError e) {
            ...
        }
    }

每一通findServiceProvider电话ServiceLoader.load。每次都会创建一个新的 ServiceLoader。这样,似乎根本没有使用ServiceLoaders缓存机制。每个调用都会扫描类路径以查找请求的ServiceProvider。

我已经尝试过的:

  1. 我知道您可以设置系统属性javax.xml.transform.TransformerFactory来指定特定的实现。这样,FactoryFinder不会使用ServiceLoader进程及其超快的速度。遗憾的是,这是jvm范围的属性,会影响在我的jvm中运行的其他java进程。例如,我的应用程序随Saxon一起提供,并且应该使用,但com.saxonica.config.EnterpriseTransformerFactory我有另一个应用程序随Saxon一起提供。设置系统属性后,其他应用程序将无法启动,因为com.saxonica.config.EnterpriseTransformerFactory其类路径中没有。因此,这似乎不是我的选择。
  2. 我已经重构了每个TransformerFactory.newInstance被称为a的地方,并缓存了TransformerFactory。但是在我的依赖项中有很多地方无法重构代码。

我的问题是:为什么FactoryFinder不重用ServiceLoader?除了使用系统属性以外,是否有办法加快整个ServiceLoader进程?不能在JDK中对此进行更改,以便FactoryFinder重用ServiceLoader实例吗?此外,这并非特定于单个FactoryFinder。javax.xml到目前为止,我对软件包中所有FactoryFinder类的行为都相同。

我正在使用OpenJDK 8/11。我的应用程序部署在Tomcat 9实例中。

编辑:提供更多详细信息

这是单个XMLInputFactory.newInstance调用的调用堆栈: 在此处输入图片说明

使用最多资源的位置是ServiceLoaders$LazyIterator.hasNextService。此方法调用getResourcesClassLoader读取META-INF/services/javax.xml.stream.XMLInputFactory文件。每次通话大约需要35毫秒。

有没有一种方法可以指示Tomcat更好地缓存这些文件,以便更快地提供它们?


我同意您对FactoryFinder.java的评估。看起来应该在缓存ServiceLoader。您是否尝试过下载openjdk源并进行构建。我知道这听起来像是一项艰巨的任务,但事实并非如此。另外,值得针对FactoryFinder.java编写问题,看看是否有人接听该问题并提供解决方案。
djhallx

您是否尝试过使用-D标志来设置属性Tomcat?例如:-Djavax.xml.transform.TransformerFactory=<factory class>.它不应覆盖其他应用程序的属性。您的帖子描述得很好,也许您已经尝试过,但我想确认一下。请参阅如何设置javax.xml.transform.TransformerFactory的系统属性如何设置Tomcat的HeapMemory或JVM参数
米哈尔Ziober

Answers:


1

35毫秒听起来好像涉及到光盘访问时间,这表明操作系统缓存存在问题。

如果类路径上有任何目录/非jar条目可能会使速度变慢。另外,如果资源不在检查的第一个位置。

ClassLoader.getResource如果可以通过配置(我已经多年没有接触过tomcat)或只是设置线程上下文类加载器了,则可以重写Thread.setContextClassLoader


听起来可能可行。我迟早会看一下。谢谢!
瓦格纳·迈克尔

1

我可以再花30分钟来调试它,并研究Tomcat如何进行资源缓存。

我特别CachedResource.validateResources感兴趣(可以在上面的火焰图中找到)。true如果CachedResource仍然有效,则返回:

protected boolean validateResources(boolean useClassLoaderResources) {
        long now = System.currentTimeMillis();
        if (this.webResources == null) {
            ...
        }

        // TTL check here!!
        if (now < this.nextCheck) {
            return true;
        } else if (this.root.isPackedWarFile()) {
            this.nextCheck = this.ttl + now;
            return true;
        } else {
            return false;
        }
    }

似乎CachedResource实际上有生存时间(ttl)。Tomcat中实际上有一种配置cacheTtl的方法,但是您只能增加此值。看起来资源缓存配置并不十分灵活。

因此,我的Tomcat的默认值配置为5000毫秒。这在进行性能测试时欺骗了我,因为我的请求之间有5秒钟多一点的时间(查看图形和内容)。这就是为什么我的所有请求基本上都在没有缓存的情况下运行并ZipFile.open每次都会触发如此繁重的原因。

因此,由于我对Tomcat配置的经验不是很丰富,因此我不确定在这里正确的解决方案是什么。增加cacheTTL可以使缓存保持更长的时间,但从长远来看并不能解决问题。

摘要

我认为这里实际上有两个罪魁祸首。

  1. FactoryFinder类不重用ServiceLoader。他们不重用它们可能有一个正当的理由-尽管我真的想不到。

  2. Tomcat在固定时间后退出Web应用程序资源(类路径中的文件-如ServiceLoader配置)的缓存

将其与未为ServiceLoader类定义系统属性相结合,您会每秒收到一个缓慢的FactoryFinder调用cacheTtl

目前,我可以将cacheTtl增加到更长的时间。我也可能会看一下汤姆·霍金斯(Tom Hawtins)关于改写的建议,Classloader.getResources即使我认为这是摆脱性能瓶颈的一种苛刻方法。也许值得一看。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.