为了账号安全,请及时绑定邮箱和手机立即绑定

FactoryFinder 性能/缓存不良

FactoryFinder 性能/缓存不良

杨魅力 2023-10-19 21:26:54
我有一个相当大的 java ee 应用程序,它有一个巨大的类路径,可以进行大量的 xml 处理。目前,我正在尝试加快一些功能的速度,并通过采样分析器找到缓慢的代码路径。我注意到的一件事是,特别是我们的代码中包含类似调用的部分TransformerFactory.newInstance(...)速度非常慢。我追踪到这个FactoryFinder方法findServiceProvider总是创建一个新ServiceLoader实例。在ServiceLoader javadoc中我发现了以下关于缓存的注释:提供者是延迟定位和实例化的,即按需定位和实例化。服务加载器维护迄今为止已加载的提供者的缓存。每次调用迭代器方法都会返回一个迭代器,该迭代器首先按实例化顺序生成缓存的所有元素,然后延迟查找并实例化任何剩余的提供程序,依次将每个元素添加到缓存中。可以通过reload方法清除缓存。到目前为止,一切都很好。这是 OpenJDK 方法的一部分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。我已经尝试过的:我知道您可以设置系统属性,例如javax.xml.transform.TransformerFactory指定特定的实现。这样FactoryFinder就不会使用ServiceLoader进程而且速度超级快。遗憾的是,这是一个 jvm 范围的属性,会影响我的 jvm 中运行的其他 java 进程。例如,我的应用程序随 Saxon 一起提供,应该使用com.saxonica.config.EnterpriseTransformerFactory我有另一个不随 Saxon 提供的应用程序。一旦我设置了系统属性,我的另一个应用程序就无法启动,因为com.saxonica.config.EnterpriseTransformerFactory它的类路径上没有。所以这对我来说似乎不是一个选择。我已经重构了调用 a 的每个地方TransformerFactory.newInstance并缓存了 TransformerFactory。但我的依赖项中有很多地方无法重构代码。我的问题是:为什么 FactoryFinder 不重用 ServiceLoader?除了使用系统属性之外,还有其他方法可以加快整个 ServiceLoader 进程吗?难道不能在 JDK 中对此进行更改,以便 FactoryFinder 重用 ServiceLoader 实例吗?此外,这并不特定于单个 FactoryFinder。javax.xml对于我迄今为止查看过的包中的所有 FactoryFinder 类,此行为都是相同的。我正在使用 OpenJDK 8/11。我的应用程序部署在 Tomcat 9 实例中。
查看完整描述

2 回答

?
Smart猫小萌

TA贡献1911条经验 获得超7个赞

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

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

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


查看完整回答
反对 回复 2023-10-19
?
慕姐4208626

TA贡献1852条经验 获得超7个赞

我可以再花 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即使我认为这是摆脱性能瓶颈的一种严厉方式。不过,这可能值得一看。


查看完整回答
反对 回复 2023-10-19
  • 2 回答
  • 0 关注
  • 119 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信