artigo
Descobrindo o Profiling de
Aplicações Java com JProfiler
Aprenda como encontrar problemas de performance na sua
aplicação com o JProfiler.
Muitas vezes, nos deparamos com problemas de performance nas aplicações
que desenvolvemos ou realizamos manutenções. A maior parte dos problemas
está relacionada com a má utilização ou insuficiência de memória. A solução
imediata de muitos é aumentar a memória alocada para a aplicação, ou
ainda, o número de nós do cluster do servidor de aplicação e finalmente o
hardware disponível. Isso tudo sem ao menos realizar uma avaliação prévia
do seu comportamento, buscando a causa-raiz dos problemas. Este artigo irá
apresentar a técnica de profiling, que visa analisar e identificar problemas
de performance em aplicações Java, utilizando a ferramenta JProfiler.
J
Diogenes Buarque Ianakiara
([email protected].
br): Está cursando o último semestre de
Análise e Desenvolvimento de Sistemas
na FIAP. Trabalha com Java desde 2006.
Atualmente é analista de desempenho
na Inmetrics.
ava Profiling é a técnica que visa analisar o desempenho de
aplicações coletando métricas e apresentando em tempo real a
porcentagem de utilização de threads, carga de objetos e tempo
de resposta dos métodos, possibilitando assim a identificação de gargalos e
oportunidades de otimização na aplicação.
Neste artigo apresentaremos um exemplo de profiling em uma aplicação
remota com a ferramenta JProfiler. Vamos desenvolver um EJB simples com
três serviços, que simularão um memory leak e um problema de consumo de
cpu. Não abordaremos os conceitos de EJB ou padrões de projetos para não
fugir do tema. Assim, iremos configurar o ambiente e o servidor de aplicação
para que possamos realizar o profiling.
Após a instalação, serão apresentadas as principais funcionalidades da ferra-
68 www.mundoj.com.br
"SUJHPt%FTDPCSJOEPP1SPmMJOHEF"QMJDBÎÜFT+BWBDPN+1SPmMFS
menta e o exemplo de uso de cada uma delas. O JProfiler irá se conectar
remotamente à aplicação desenvolvida e iremos por fim realizar a análise
e identificar o problema da aplicação.
selecione a plataforma remota. No nosso caso, selecionaremos “Linux/
AMD64”, como mostra a figura 3.
O memory leak, ou vazamento de memória, ocorre
quando algum componente da nossa aplicação está
utilizando uma determinada quantidade de memória
para realizar alguma operação e, após finalizar esta
operação, este componente não libera a memória
utilizada. Geralmente isto ocorre devido a erros de programação e pode levar ao consumo total da memória.
'JHVSB4FMFDJPOBOEPPUJQPEFQSPmMJOHFP4JTUFNB0QFSBDJPOBM
Instalação e configuração do JProfiler
O JProfiler é uma ferramenta paga, porém a ej-technologies, fornecedora
da ferramenta, oferece 10 dias de licença Trial, que até a data de escrita
deste artigo está na versão 5.2.3. O download pode ser realizado no site
http://www.ej-technologies.com/products/jprofiler/overview.html.
Configuração 1.1
Uma vez terminada a instalação padrão, a primeira execução apresentará
um wizard de inicialização rápida, para configurar o seu primeiro profiling. Na primeira tela, selecione a opção de profiling em uma aplicação
local ou remota “An application Server, locally or remotely”, como mostra
a figura 1.
Na terceira etapa, existem três possibilidades:
t "HVBSEBS QPS DPOFYÜFT BUSBWÏT EB (6* EP +1SPmMFS i8BJU GPS B
connection from the JProfiler GUI”. Com esta opção, o JProfiler configura o servidor de aplicação para que ele aguarde o JProfiler se
conectar antes de iniciar. Caso o JProfiler não se conecte, o servidor
de aplicação não será inicializado).
t *OJDJBS JNFEJBUBNFOUF i4UBSUVQ JNNFEJBUFMZ DPOOFDU MBUFS XJUI
the JProfiler GUI”, O Jprofiler permite que o servidor inicie normalmente, possibilitando uma conexão futura com o JProfiler).
t 3FBMJ[BS P QSPmMFS P÷JOF i+È B PQÎÍPi1SPmMF P÷JOF +1SPmMF (6*
cannot connect” possibilita a realização do profiling em modo offline. Com esta opção, é possível configurar o servidor de aplicação
para gravar os dados do profiling em um arquivo e assim possibilitar uma análise off-line).
No nosso exemplo, vamos selecionar a opção iniciar imediatamente,
conforme a figura 4.
Figura 1. Selecionando o tipo de profiling.
Em seguida, será necessário seguir 11 etapas para configurar o profiling.
Na figura 2, é apresentada a primeira etapa, escolha o fornecedor e versão do servidor de aplicação. Nesse caso, selecione JBoss 5.x.
Figura 4. Selecionando o tipo de inicialização do Servidor de Aplicação.
Agora defina o endereço do servidor remoto, seguindo o exemplo da
figura 5.
'JHVSB*OGPSNBOEPPFOEFSFÎPEPTFSWJEPSEFBQMJDBÎÍP
Figura 2. Selecionando o servidor de aplicação.
Na segunda etapa, se a aplicação estiver no mesmo servidor em que
está instalado o JProfiler, selecione “On this computer”. Caso contrário,
Na quinta etapa, o JProfiler deve ser instalado no servidor. Para isto,
existe duas maneiras:
1 – Next, Next, Finish;
69
"SUJHPt%FTDPCSJOEPP1SPmMJOHEF"QMJDBÎÜFT+BWBDPN+1SPmMFS
2 – Copiar os arquivos de uma instalação préexistente para a mesma
versão de SO.
No próximo passo, devemos informar fornecedor, versão do Java e o
modo de compilação, conforme a figura 9.
O diretório da instalação deve ser informado nessa etapa, como mostra
a figura 6.
'JHVSB*OGPSNBOEPBWFSTÍPEP+BWB
'JHVSB*OGPSNBOEPPEJSFUØSJPEP+1SPmMFSOPTFSWJEPS
Na sexta etapa, precisamos informar o diretório onde estará o arquivo de
configuração de acesso remoto ao JProfiler, e é de extrema importância
para o sucesso do profiling. O nome deste arquivo é config.xml e ele será
gerado automaticamente ao final deste wizard no diretório “C:\Documents and Settings\<usuario>\.jprofiler5\config.xml” para a plataforma
Windows e “/home/<usuario>/.jprofiler5/” para a plataforma Linux/Unix.
Utilize a figura 7 como referência.
Como é apresentado na figura 10, o JProfiler utiliza a configuração default, para definir a porta de comunicação. A mesma pode ser alterada
caso já esteja em uso.
'JHVSB*OGPSNBOEPBQPSUBEFDPNVOJDBÎÍP
Aqui finalizamos nossa configuração, podemos visualizar as informações
sobre este profiling e todos os cuidados que devemos ter antes de iniciar
o profiling, como mostra a figura 11. Na 11ª etapa, você pode escolher
entre iniciar imediatamente o profiling ou deixar para depois.
'JHVSB*OGPSNBOEPPEJSFUØSJPEP+1SPmMFSOPTFSWJEPS
É possível observar na figura 8 que agora será necessário acessar o script
de inicialização do servidor de aplicação para o JProfiler configurar os parâmetros de inicialização necessários. Para esta configuração, compartilhe
o diretório do servidor aplicação com as devidas permissões de escrita.
Assim, para o JBoss na plataforma Windows acesse o script run.bat no ou
run.sh na plataforma Linux/Unix. Após clicar em Next o JProfiler irá gerar
um novo script na mesma pasta indicada, chamado run_jprofiler.sh.
Figura 11. Finalizando a configuração do profiling.
Figura 8. Acessando o script de inicializar do servidor.
70 www.mundoj.com.br
O processo de profiling geralmente é muito oneroso para o
processador. Por isso é importante definir um período curto
para a execução do profiling. Outro fator importante é o
nível de detalhamento do profiling que deve ser definido
antes de sua execução, o nível médio é recomendado para
CPU e o baixo para memória. Caso seu servidor esteja com
folga no uso de memória e CPU, estes níveis podem ser
alterados conforme sua necessidade.
"SUJHPt%FTDPCSJOEPP1SPmMJOHEF"QMJDBÎÜFT+BWBDPN+1SPmMFS
Para podermos executar o profiling, vamos desenvolver uma miniaplicação que simulará o consumo de CPU e o Memory leak. Construiremos um
EJB contendo três serviços. O primeiro serviço simulará um vazamento
de memória, o segundo simulará o processamento de uma request simples e, por ultimo, teremos um método que terá a uma maior alocação
de memória e processamento, entretanto não possuirá vazamento de
memória.
Implementação dos serviços 1.1
A Listagem 1 apresenta uma condição de errada no segundo "for", devido memory leak que o objeto string tem em cada iteração. Se fosse
um registro de um banco de dados, este vazamento poderia resultar em
uma falta de resposta da aplicação e esgotar rapidamente da memória
disponível.
Listagem 1. Implementação do método memoryLeak.
Listagem 3. Implementação do método noMemoryLeak.
public void noMemoryLeak (int size) {
HashSet tmpStore = new HashSet();
for (int i=0; i<size; ++i) {
String leakingUnit = new String(“Object: “ + i);
tmpStore.add(leakingUnit);
}
}
}
Realizando o profiling e identificando
o problema
Primeiramente vamos abrir o JProfiler e selecionar a opção “Start Center”.
Dentre as opções disponíveis, selecione a opção que você configurou no
primeiro tópico, e clique em Start. Conforme a figura 12.
public void memoryLeak(int iter, int count) {
for (int i=0; i<iter; i++) {
for (int n=0; n<count; n++) {
memoryVector.add(Integer.toString(n+i));
}
for (int n=count-1; n>0; n--) {
memoryVector.removeElementAt(n);
}
}
}
A Listagem 2 apresenta um caso muito comum, quando uma requisição de entrada é mantida em uma hash table até que a mesma seja
concluída, exceto neste exemplo, deixamos de propósito um erro de
programação, no qual não removemos do hash table os objetos que
foram inseridos anteriormente. Durante um período de tempo, a hash
table terá um grande número de alocações, o que resultará em muitas
colisões, bem como uma grande parte da pilha ocupada por entradas
inúteis. Ambas as listagens são casos muito comuns que resultam em
memory leaks.
Figura 12. Selecionando a o profiling configurado.
O wizard apresentado na figura 13 dá a possibilidade de configurar os
níveis de detalhamento do profiling, iremos utilizar os níveis recomendados para não degradar o ambiente testado. Clique em ok para iniciar
o profiling.
Listagem 2. Implementação do método requestMemoryLeak.
public void requestMemoryLeak(int iter) {
Random requestQueue = new Random();
for (int i=0; i<iter; i++) {
int newRequest = requestQueue.nextInt();
pendingRequests.add(new Integer(newRequest));
}
}
Podemos pensar que os locais de maior alocação de memória são os
dois métodos anteriores. No entanto, isso não é verdade. Por exemplo,
na Listagem 3, o método noMemoryLeak aloca uma grande quantidade
de memória, porém tudo isso é coletado pelo GC (Garbage Collector),
deixando a memória sempre disponível. Por outro lado, os outros dois
métodos que não alocam muita memória estão causando constantemente memory leaks.
'JHVSB*OJDJBOEPPQSPmMJOH
Agora vamos iniciar nossa aplicação cliente, que por sua vez consumirá
os serviços do nosso componente MemoryLeakEJB. A Listagem 4 apresenta o código que simulará a utilização dos serviços disponíveis. Inicie
a aplicação cliente.
71
"SUJHPt%FTDPCSJOEPP1SPmMJOHEF"QMJDBÎÜFT+BWBDPN+1SPmMFS
Listagem 4. Implementação de uma aplicação Cliente.
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(“java.naming.factory.initial”,”org.jnp.interfaces.
NamingContextFactory”);
properties.put(“java.naming.factory.url.pkgs”,”=
org.jboss.naming:org.jnp.interfaces”);
properties.put(“java.naming.provider.url”,”10.10.11.97:1099”);
try
{
Context context = new InitialContext(properties);
MemoryLeakRemote memoryLeakRemote =
(MemoryLeakRemote) context.lookup(MemoryLeakBean.
RemoteJNDIName);
for (int i=0; true; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sentado um botão ao qual será possível iniciar o monitoramento que
desejamos. Para nosso exemplo, vamos utilizar apenas as aba inicial, All
Objects e Allocation Call Tree.
O ideal é avaliar um item por vez, seja ele memória, CPU,
threads, ou snapshots. Para não degradar totalmente o
ambiente. Uma que você iniciou uma avaliação é importante armazenar todas as evidências do que foi analisado,
para futuras comparações, como também para um histórico do comportamento da aplicação.
Após identificar que o componente MemoryLeakBean está na lista de
objetos da aba All Objects, selecione a aba Allocation Call Tree. Esta aba
apresentará uma árvore de utilização da memória.
Como podemos observar na imagem da figura 15, os métodos do
componente MemoryLeakBean consomem aproximadamente 50% do
tempo de uso e alocação de memória, especialmente o método noLeak.
System.out.println(“iteração nº: “ + i);
memoryLeakRemote.slowlyLeakingVector(1000,10);
memoryLeakRemote.leakingRequestLog(5000);
memoryLeakRemote.noLeak(100000);
}
} catch (NamingException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
Identificando problemas de vazamento de memória
Neste momento, o JProfiler está conectado com o nosso servidor de
aplicação. A primeira métrica que o JProfiler nos apresenta é a “Memory
Views”. Estes dados dão a possibilidade de visualizar os níveis de agregação, podemos selecionar os níveis Class, Packages, J2EE Components.
Para nosso exemplo, selecionaremos J2EE Components. Esta opção apresenta os componentes JEE mais utilizados no App Server, geralmente
ele também apresenta componentes do próprio servidor de aplicação,
porém podemos observar na figura 14 que dentre eles encontramos o
nosso componente “MemoryLeakBean”.
'JHVSB7JTVBMJ[BOEPNÏUSJDBTEFNFNØSJBEBBCB"MMPDBUJPO$BMM5SFF
Agora vamos selecionar a opção VM Telemetry Views. Esta opção apresentará gráficos de utilização da Heap, Thread e até CPU load. Ao selecionar
pela primeira vez esta opção, já podemos observar um comportamento
incomum da Heap. Notamos na figura 16 que este comportamento representa um memory leak ou vazamento de memória.
'JHVSB7JTVBMJ[BOEPPHSÈmDPEFNFNØSJB)FBQ
Figura 14. Visualizando métricas de memória da Aba All Objects.
As abas da parte inferior nos apresentam métricas diversificadas do uso
de memória no servidor de aplicação. Ao selecionar uma aba, será apre72 www.mundoj.com.br
Indo mais a fundo, vamos selecionar a aba GC Activity, que nos apresentará um gráfico da atividade do Garbage Collector. Podemos notar na figura 17 uma intensa atividade de GC, este comportamento se dá quando
possuímos muitos novos objetos alocados, preenchendo todo espaço da
Heap. Caso não haja mais memória para alocação, será lançado o “velho
e conhecido” erro java.lang.OutOfMemory.
"SUJHPt%FTDPCSJOEPP1SPmMJOHEF"QMJDBÎÜFT+BWBDPN+1SPmMFS
Após identificar as causas de um problema de performance, deve-se analisar profundamente o problema para se obter a melhor solução. Muitas
vezes estes problemas podem ser resolvidos com otimizações do código,
porém nem sempre podemos alterar o mesmo, por exemplo, quando o
código não nos pertence. Outros problemas podem ser resolvidos com
o tuning da JVM.
Tuning da JVM é o ato de otimizar o uso do garbage
collector para que ele seja executado de forma mais eficiente, possibilitando assim um ganho de performance
na aplicação.
Figura 17. Visualizando o gráfico de atividade do GC.
A opção CPU Views apresenta detalhadamente a árvore de uso de CPU.
Podemos observar na figura 18 que 95% da utilização de CPU está alocada nos três métodos que estamos consumindo. Podemos filtrar ainda
mais utilizando a opção Thread status. Esta opção fornece a visualização
detalhada do uso de classes e métodos por estado de cada thread, seja
ele blocked, running ou waiting.
Não está no escopo do artigo se aprofundar em como resolver os problemas de performance ou como desenvolver um EJB. Todos os códigos
estarão disponíveis no site http://www.mundoj.com.br.
Saber mais
Identificando problemas de consumo de CPU
Na edição 35 da revista Mundoj o artigo “Conhecendo os parâmetros de configuração mais
utilizados da JVM” apresentou técnicas de como
otimizar a JVM.
Considerações finais
Neste artigo foi apresentadas algumas formas de identificar problemas
de performance em aplicações Java utilizando a técnica de profiling. Esta
técnica está fortemente ligada ao ciclo de vida das aplicações, uma vez
que uma aplicação já nasce com problemas de performance, certamente
estes problemas só irão aumentar. Assim buscamos apresentar não só os
conceitos do profiling, mas também um exemplo prático de como identificar esses problemas.
Figura 18. Visualizando a árvore de consumo de CPU.
As abas Hot Spots apresentam tanto para memória ou CPU pontos que
possivelmente podem ser críticos, porém estas métricas requerem um
maior nível de detalhamento do profiling. Muitas vezes estas métricas
podem apresentar resultados genéricos, como java.lang.String ou java.
lang.Integer, entretanto, se você possuir um método muito oneroso para
o sistema, ele com certeza aparecerá nesta análise. Como podemos observar o método noLeak da figura 19.
No primeiro tópico apresentamos como configurar a ferramenta e construir um profiler remoto, seguindo, construímos duas aplicações, um
EJB que simulou problemas de memory leak e cpu load e uma aplicação cliente que por sua vez consumiu todos os serviços do EJB. Assim,
aprendemos como iniciar um profiling visualizando métricas de memória
e cpu e como identificar não só o componente, mas também a classe e os
métodos que apresentavam os problemas de performance. É importante
salientar que apenas identificando o gargalo não necessariamente o
problema estará resolvido. É preciso analisar e evidenciar para se chegar
BVNBTPMVÎÍPFGFUJWBt
Referências
IUUQSFTPVSDFTFKUFDIOPMPHJFTDPNKQSPmMFSIFMQEPD
IUUQXXXKBWBXPSMEDPNKBWBXPSMEKBWBRBRBDQVIUNM
IUUQXXXJCNDPNEFWFMPQFSXPSLTSBUJPOBMMJCSBSZ@(VQUB1BMBOLJ
IUUQXXXMBMJMVOBEFFKCUVUPSJBMKCPTTIUNM
IUUQXXXNPVTFPWFSTUVEJPDPNCMPHQSPmMJOHFNKBWBDPNKQSPmMFS
IUUQKBWBE[POFDPNBSUJDMFTKQSPmMFSZPVSKBWBDPEFDPVME
Figura 19. Visualizando possíveis pontos de interesse.
73
Download

Descobrindo o Profiling de