Eu tenho um aplicativo java ee bastante grande com um enorme caminho de classe fazendo muito processamento xml. Atualmente, estou tentando acelerar algumas de minhas funções e localizar caminhos de código lento por meio de criadores de perfil de amostragem.
Uma coisa que notei é que especialmente partes do nosso código nas quais temos chamadas TransformerFactory.newInstance(...)
são desesperadamente lentas. Eu rastreei isso para o FactoryFinder
método findServiceProvider
sempre criando uma nova ServiceLoader
instância. No ServiceLoader
javadoc , encontrei a seguinte nota sobre cache:
Os fornecedores são localizados e instanciados preguiçosamente, ou seja, sob demanda. Um carregador de serviço mantém um cache dos provedores que foram carregados até o momento. Cada chamada do método iterador retorna um iterador que primeiro gera todos os elementos do cache, em ordem de instanciação, e depois localiza e instiga preguiçosamente todos os provedores restantes, adicionando cada um deles ao cache. O cache pode ser limpo através do método recarregar.
Por enquanto, tudo bem. Isso faz parte do FactoryFinder#findServiceProvider
método OpenJDKs :
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) {
...
}
}
Toda chamada para findServiceProvider
chamadas ServiceLoader.load
. Isso cria um novo ServiceLoader a cada vez. Dessa forma, parece que não há nenhum uso do mecanismo de cache do ServiceLoaders. Toda chamada verifica o caminho de classe para o ServiceProvider solicitado.
O que eu já tentei:
- Eu sei que você pode definir uma propriedade do sistema
javax.xml.transform.TransformerFactory
para especificar uma implementação específica. Dessa forma, o FactoryFinder não usa o processo ServiceLoader e é super rápido. Infelizmente, essa é uma propriedade ampla da jvm e afeta outros processos java em execução na minha jvm. Por exemplo, meu aplicativo é enviado com o Saxon e devo usar.com.saxonica.config.EnterpriseTransformerFactory
Eu tenho outro aplicativo que não é fornecido com o Saxon. Assim que eu defino a propriedade do sistema, meu outro aplicativo falha ao iniciar, porque não existecom.saxonica.config.EnterpriseTransformerFactory
no caminho de classe. Portanto, isso não parece ser uma opção para mim. - Eu já refatorei todos os lugares onde a
TransformerFactory.newInstance
é chamado e coloco em cache o TransformerFactory. Mas existem vários lugares nas minhas dependências onde não posso refatorar o código.
Minhas perguntas são: Por que o FactoryFinder não reutiliza um ServiceLoader? Existe uma maneira de acelerar todo esse processo do ServiceLoader além de usar propriedades do sistema? Isso não pôde ser alterado no JDK para que um FactoryFinder reutilize uma instância do ServiceLoader? Além disso, isso não é específico para um único FactoryFinder. Esse comportamento é o mesmo para todas as classes do FactoryFinder no javax.xml
pacote que eu analisei até agora.
Estou usando o OpenJDK 8/11. Meus aplicativos são implantados em uma instância do Tomcat 9.
Editar: fornecendo mais detalhes
Aqui está a pilha de chamadas para uma única chamada XMLInputFactory.newInstance:
Onde está a maioria dos recursos ServiceLoaders$LazyIterator.hasNextService
. Este método chama o getResources
ClassLoader para ler o META-INF/services/javax.xml.stream.XMLInputFactory
arquivo. Só essa ligação leva cerca de 35ms de cada vez.
Existe uma maneira de instruir o Tomcat a armazenar em cache melhor esses arquivos para que sejam exibidos mais rapidamente?
fonte
-D
sinalizador no seuTomcat
processo? Por exemplo:-Djavax.xml.transform.TransformerFactory=<factory class>.
ele não deve substituir as propriedades de outros aplicativos. Sua postagem está bem descrita e você provavelmente já tentou, mas gostaria de confirmar. Consulte Como definir propriedade do sistema javax.xml.transform.TransformerFactory , Como definir HeapMemory ou JVM Arguments no TomcatRespostas:
35 ms parece que há tempos de acesso ao disco envolvidos e isso indica um problema com o cache do SO.
Se houver alguma entrada de diretório / não jar no caminho de classe que possa atrasar as coisas. Além disso, se o recurso não estiver presente no primeiro local verificado.
ClassLoader.getResource
pode ser substituído se você pode definir o carregador de classes de contexto de encadeamento, através da configuração (não toquei no tomcat há anos) ou apenasThread.setContextClassLoader
.fonte
Eu poderia ter outros 30 minutos para depurar isso e observei como o Tomcat faz o Cache de Recursos.
Em particular
CachedResource.validateResources
(que pode ser encontrado no gráfico acima) era de meu interesse. Retornatrue
se oCachedResource
ainda for válido:Parece que um CachedResource realmente tem um tempo de vida (ttl). Na verdade, existe uma maneira no Tomcat de configurar o cacheTtl, mas você só pode aumentar esse valor. A configuração do cache de recursos não é realmente flexível com facilidade.
Portanto, meu Tomcat tem o valor padrão de 5000 ms configurado. Isso me enganou durante os testes de desempenho, porque eu tinha um pouco mais de 5 segundos entre minhas solicitações (olhando gráficos e outras coisas). É por isso que todos os meus pedidos eram executados basicamente sem cache e eram
ZipFile.open
sempre pesados .Portanto, como não tenho muita experiência com a configuração do Tomcat, ainda não tenho certeza de qual é a solução certa aqui. Aumentar o cacheTTL mantém os caches por mais tempo, mas não corrige o problema a longo prazo.
Sumário
Eu acho que existem dois culpados aqui.
Classes do FactoryFinder não reutilizando um ServiceLoader. Pode haver uma razão válida para que eles não os reutilizem - eu realmente não consigo pensar em um.
Tomcat removendo caches após um tempo fixo para recursos de aplicativos da web (arquivos no caminho de classe - como uma
ServiceLoader
configuração)Combine isso com não ter definido a propriedade do sistema para a classe ServiceLoader e você receberá uma chamada lenta do FactoryFinder a cada
cacheTtl
segundo.Por enquanto, eu posso viver aumentando o cacheTtl por mais tempo. Também posso dar uma olhada na sugestão de substituição de Tom Hawtins,
Classloader.getResources
mesmo que eu ache que essa é uma maneira dura de se livrar desse gargalo de desempenho. Pode valer a pena olhar embora.fonte