我有一个相当大的Java ee应用程序,它具有执行许多xml处理的巨大类路径。目前,我正在尝试加快某些功能的速度,并通过采样分析器定位慢速代码路径。
我注意到的一件事是,特别是我们在其中调用了诸如TransformerFactory.newInstance(...)
之类的代码的部分非常慢。我一直跟踪到FactoryFinder
方法findServiceProvider
,始终创建一个新的ServiceLoader
实例。在ServiceLoader
javadoc中,我找到了有关缓存的以下说明:
到现在为止还挺好。这是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
。因此,这似乎不是我的选择。 TransformerFactory.newInstance
的每个地方,并缓存了TransformerFactory。但是在我的依赖项中有很多地方无法重构代码。 我的问题是:
为什么FactoryFinder不重用ServiceLoader?除了使用系统属性以外,是否有办法加快整个ServiceLoader进程?不能在JDK中对此进行更改,以便FactoryFinder重用ServiceLoader实例吗?此外,这并非特定于单个FactoryFinder。到目前为止,我对
javax.xml
包中的所有FactoryFinder类的行为都相同。我正在使用OpenJDK 8/11。我的应用程序部署在Tomcat 9实例中。
编辑:提供更多详细信息
这是单个XMLInputFactory.newInstance调用的调用堆栈:
使用大多数资源的位置在
ServiceLoaders$LazyIterator.hasNextService
中。此方法在ClassLoader上调用getResources
以读取META-INF/services/javax.xml.stream.XMLInputFactory
文件。每次通话大约需要35毫秒。有没有一种方法可以指示Tomcat更好地缓存这些文件,以便更快地提供它们?
最佳答案
35毫秒听起来像涉及到光盘访问时间,这表明操作系统缓存存在问题。
如果类路径上有任何目录/非jar条目可能会使速度变慢。另外,如果在检查的第一个位置上不存在该资源。
如果可以通过配置(我已经多年没有接触过tomcat)或只是ClassLoader.getResource
来设置线程上下文类加载器,则可以覆盖Thread.setContextClassLoader
。