Back-End

13 mar, 2014

Investigando vazamento de memória: análise do problema – Parte 02

Publicidade

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:

  1. Faça um dump da pilha que está vazando no servidor.
  2. Use o dump para gerar um relatório.
  3. Analise o relatório.

Existem várias ferramentas que você pode usar para criar um arquivo de dump de pilha. Estes incluem:

  1. jconsole
  2. jvisualvm
  3. 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.

memoria-1

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.

memoria-2

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.

memoria-3

Utilizar o MAT é uma questão de clicar em “Adquire Heap Dump” e de seguir as instruções.

memoria-4

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.

memoria-5

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:

memoria-6

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:

memoria-7

Escolha: Leak Suspect Report. O MAT vai agora produzir por alguns segundos uma página que se parece com isto:

memoria-8

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…

memoria-9

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 >>.

memoria-10

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.