O primeiro artigo desta mini-série voltou-se para a criação de um aplicativo de exemplo com muitos vazamentos, para que pudéssemos investigar técnicas para resolver os problemas de heap em aplicativos de servidor. Isso demonstra o grande problema com o padrão Producer-Consumer, ou seja, de que o código do consumer precisa ser capaz de remover itens da fila muito rápido, ou mais rápido, do que o producer. O artigo terminou comigo iniciando o código de teste e esperando ele vazar memória suficiente para começar a investigação. Agora é hora de fazer essa investigação.
Se você ler a primeira parte deste artigo, saberá que o código de vazamento faz parte de um aplicativo* que registra ordens de ações em um banco de dados fictício usando o padrão Producer Consumer. O código de exemplo foi escrito para conter uma falha muito óbvia, ou seja, que o OrderRecord não possa acompanhar o OrderFeed. Isso significa que a fila de Order fica cada vez maior até que o aplicativo finalmente seja executado fora do espaço de pilha e falhe. Olhando para o meu código, o problema deveria ser óbvio, mas e se você nunca viu o código antes e trata-se de um enorme e complexo código de uso industrial, além de que não há nenhuma thread de monitoramento para ficar de olho no tamanho da fila ou em outros detalhes internos? O que você faz então?
Há três passos necessários para encontrar o problema com uma aplicação de fuga:
- Faça um dump da pilha que está vazando no servidor.
- Use o dump para gerar um relatório.
- Analise o relatório.
Existem várias ferramentas que você pode usar para criar um arquivo de dump de pilha. Estes incluem:
- jconsole
- jvisualvm
- eclipse Memory Analyser Tool (MAT)
Criar um dump com jconsole
Conecte o jconsole à sua aplicação. Clique na aba MBeans e abra o pacote com.sun.management. Em seguida, clique em HotSpotDiagnostic. Abra Operations e selecione dumpheap. Você verá a operação de dumpheap, que tem dois parâmetros p0 e p1. Digite um nome do arquivo para o dump na caixa p0 e pressione o botão dumpHeap.
Criar um heap dump com o jvisualvm
Quando conectado ao código de exemplo, clique com o botão direito sobre a sua aplicação no painel “application” à esquerda e selecione “Heap Dump”.
Note que se você estiver em uma conexão remota com o servidor com vazamento, então jvisualvm armazenará o arquivo de dump no diretório /tmp da máquina remota (assumindo que é uma máquina Unix). Você vai ter que transferir esse arquivo para a sua máquina para uma análise mais aprofundada.
Criar um dump da pilha com MAT
Apesar de o jconsole e de o jvisualvm fazerem parte do JDK, o MAT, ou ferramenta de análise de memória (na sigla em inglês), é uma ferramenta para o eclipse que você pode baixar de eclipse.org.
A versão atual do MAT requer o jdk 1.6 instalado no seu PC. Se você estiver usando o Java 1.7 , não se preocupe, ele vai instalar a versão 1.6 para você e não vai atrapalhar o resto da sua máquina e a versão 1.7 padrão.
Utilizar o MAT é uma questão de clicar em “Adquire Heap Dump” e de seguir as instruções.
Conexões remotas
A primeira coisa a notar aqui é que se você está tentando descobrir por que um servidor em produção está caindo, então você provavelmente vai ter que conectar remotamente usando o JMX e, para isso, você precisará das seguintes opções da linha de comando:
-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
Quando fazer um dump do heap
Isso necessita de um pouco de reflexão e um pouco de sorte. Se você tirar o seu dump cedo demais, então pode não ver os problemas, porque eles estarão mascarados por instâncias de classe legítimas; no entanto, não espere muito tempo porque tirar um dump de pilha requer memória e, portanto, o ato de tirar um dump de uma pilha pode fazer o seu aplicativo falhar.
A melhor ideia é anexar o jconsole ao seu aplicativo e monitorar a sua pilha até parecer que ela está à beira do colapso. Isso é fácil de identificar com todos os três indicadores das seções heap em verde:
Análise do dump
Esta é a hora em que o MAT se destaca, já que foi projetado para analisar dumps. Para abrir e analisar um dump, selecione File> Open Heap Dump. Depois de escolher o seu arquivo de dump, serão dadas três opções como as mostradas abaixo:
Escolha: Leak Suspect Report. O MAT vai agora produzir por alguns segundos uma página que se parece com isto:
O gráfico demonstra que, nesse caso, há um suspeito principal do vazamento. Você pode estar pensando que isso é bem fácil de corrigir, pois trata-se de um código de teste, mas o que você esperava? Sim, nesse caso, tudo fica bem claro; o suspeito “a” usa 98,7 MB da memória, enquanto o resto dos objetos na memória usa os outros 1,5 MB. O fato é que você verá gráficos que se parecem com esse em situações de vazamentos de memória na vida real.
A próxima coisa a fazer é cavar um pouco mais…
A próxima parte do relatório, mostrado acima, nos diz que há uma LinkedBlockingQueue que está usando 98,46 % da memória. Para investigar isso ainda mais, clique em Details >>.
Isso revela que o problema está na verdade em nosso orderQueue, que é acessado pelos três objetos do meu artigo anterior: OrderFeed, OrderRecord e OrderMonitor e, como sabemos a partir do código, contém um monte de objetos Order.
Então é isso; o MAT nos disse que o código de exemplo tem uma LinkedBlockingQueue que está usando todo espaço de heap do aplicativo de teste causando enormes problemas. Ele não nos disse por que isso está acontecendo, e você realmente não pode esperar que ele faça isso. Essa é uma questão de, como diria Agatha Christie através de seu personagem Hercule Poirot, para se usar “as pequenas células cinzentas”…
Obrigado a Annika Kapur por me enviar o link deste blog muito útil sobre vazamento de memória: Hynting Memory Leaks in Java, por José Ferreira de Souza Filho.
***
Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.captaindebug.com/2013/12/investigating-memory-leaks-part-2.html#.Uxi8sNsamRa
* O código fonte pode ser encontrado no meu projeto Producer Consumer do Github.