我有一个相当大的java ee应用程序,它有一个巨大的类路径,可以进行大量的xml处理。目前,我正在尝试加快一些功能的速度,并通过采样分析器找到缓慢的代码路径。
我注意到的一件事是,特别是我们的代码中,我们有像 TransformerFactory.newInstance(...)
这样的调用。非常慢。我追踪到FactoryFinder
方法findServiceProvider
总是创造一个新的ServiceLoader
实例。在 ServiceLoader
javadoc我发现以下有关缓存的注释:
Providers are located and instantiated lazily, that is, on demand. A service loader maintains a cache of the providers that have been loaded so far. Each invocation of the iterator method returns an iterator that first yields all of the elements of the cache, in instantiation order, and then lazily locates and instantiates any remaining providers, adding each one to the cache in turn. The cache can be cleared via the reload method.
到目前为止一切顺利。这是 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进程而且速度 super 快。遗憾的是,这是一个 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
。此方法调用getResources
在 ClassLoader 上读取 META-INF/services/javax.xml.stream.XMLInputFactory
文件。每次仅该调用就需要大约 35 毫秒。
有没有办法指示 Tomcat 更好地缓存这些文件,以便更快地提供它们?
最佳答案
35 毫秒 听起来像是涉及磁盘访问时间,这表明操作系统缓存存在问题。
类路径上是否有任何目录/非 jar 条目可能会减慢速度。此外,如果资源不存在于检查的第一个位置。
如果您可以设置线程上下文类加载器,则可以覆盖ClassLoader.getResource
,可以通过配置(我已经多年没有接触过tomcat)或者只是Thread.setContextClassLoader
.
关于java - FactoryFinder 性能/缓存不良,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58376695/