У меня довольно большое приложение 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 wide и влияет на другие процессы java, работающие в моем jvm. Например, мое приложение поставляется с Saxon и должно использовать com.saxonica.config.EnterpriseTransformerFactory
У меня есть другое приложение, которое не поставляется с Saxon. Как только я установлю системное свойство, мое другое приложение не сможет запуститься, потому что в его classpath нет com.saxonica.config.EnterpriseTransformerFactory
. Так что это, кажется, не вариант для меня. - Я уже провела рефакторинг каждого места, где вызывается
TransformerFactory.newInstance
, и кешировала TransformerFactory. Но в моих зависимостях есть разные места, где я не могу реорганизовать код.
Мои вопросы: почему FactoryFinder не использует ServiceLoader повторно? Есть ли способ ускорить весь этот процесс ServiceLoader, кроме использования системных свойств? Разве это не может быть изменено в JDK, чтобы FactoryFinder повторно использовал экземпляр ServiceLoader? Также это не относится только к одному FactoryFinder. Это поведение одинаково для всех классов FactoryFinder в пакете javax.xml
, который я рассмотрел до сих пор.
Я использую OpenJDK 8/11. Мои приложения развернуты в экземпляре Tomcat 9.
Редактировать: Предоставление более подробной информации
Вот стек вызовов для одного вызова XMLInputFactory.newInstance: 
Где используется большинство ресурсов, это ServiceLoaders$LazyIterator.hasNextService
. Этот метод вызывает getResources
в ClassLoader для чтения файла META-INF/services/javax.xml.stream.XMLInputFactory
. Один только этот вызов занимает около 35 мс каждый раз.
Есть ли способ указать Tomcat лучше кэшировать эти файлы, чтобы они обслуживались быстрее?