Como encontrar um vazamento de memória Java

142

Como você encontra um vazamento de memória em Java (usando, por exemplo, JHat)? Eu tentei carregar o despejo de pilha no JHat para dar uma olhada básica. No entanto, eu não entendo como devo encontrar a referência raiz ( ref ) ou como ele é chamado. Basicamente, posso dizer que existem várias centenas de megabytes de entradas da tabela de hash ([java.util.HashMap $ Entry ou algo assim), mas os mapas são usados ​​em todo o lugar ... Existe alguma maneira de procurar mapas grandes , ou talvez encontre raízes gerais de grandes árvores de objetos?

[Edit] Ok, eu li as respostas até agora, mas vamos apenas dizer que sou um bastardo barato (o que significa que estou mais interessado em aprender a usar o JHat do que pagar pelo JProfiler). Além disso, o JHat está sempre disponível, pois faz parte do JDK. A não ser, é claro, que JHat não tenha outra maneira senão a força bruta, mas não acredito que possa ser esse o caso.

Além disso, acho que não vou conseguir modificar (adicionando logs de todos os tamanhos de mapa) e executá-lo por tempo suficiente para que eu note o vazamento.

jwiklund
fonte
Este é outro "voto" para o JProfiler. Funciona muito bem para análise de heap, possui uma interface de usuário decente e funciona muito bem. Como diz o McKenzieG1, US $ 500 é mais barato do que o tempo que você gastaria procurando a fonte desses vazamentos. Quanto ao preço das ferramentas, não é ruim.
joev 02/09/08
A Oracle tem uma página relevante aqui: docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/…
Laurel

Respostas:

126

Eu uso a seguinte abordagem para encontrar vazamentos de memória em Java. Eu usei o jProfiler com grande sucesso, mas acredito que qualquer ferramenta especializada com recursos gráficos (os diffs são mais fáceis de analisar em forma gráfica) funcionará.

  1. Inicie o aplicativo e aguarde até chegar ao estado "estável", quando toda a inicialização estiver concluída e o aplicativo estiver ocioso.
  2. Execute a operação suspeita de produzir um vazamento de memória várias vezes para permitir que ocorra qualquer inicialização relacionada ao banco de dados.
  3. Execute o GC e tire um instantâneo da memória.
  4. Execute a operação novamente. Dependendo da complexidade da operação e do tamanho dos dados processados, talvez seja necessário executar várias ou várias vezes.
  5. Execute o GC e tire um instantâneo da memória.
  6. Execute um diff para 2 snapshots e analise-o.

Basicamente, a análise deve começar com a maior diferença positiva, digamos, com os tipos de objetos e descobrir o que faz com que esses objetos extras fiquem na memória.

Para aplicativos da Web que processam solicitações em várias threads, a análise fica mais complicada, mas, no entanto, a abordagem geral ainda se aplica.

Realizei vários projetos especificamente voltados para a redução da pegada de memória dos aplicativos e essa abordagem geral com alguns ajustes e truques específicos do aplicativo sempre funcionou bem.

Dima Malenko
fonte
7
A maioria dos criadores de perfil Java (se não todos) fornece uma opção para chamar o GC com o clique de um botão. Ou você pode chamar System.gc () do local apropriado no seu código.
Dima Malenko
3
Mesmo se chamarmos System.gc (), a JVM pode optar por negligenciar a chamada. AFAIK, isso é específico da JVM. +1 na resposta.
Aniket Thakur
4
O que exatamente é um "instantâneo de memória?" Existe algo que me diga o número de cada tipo de objeto que meu código está executando?
gnomed 22/05/2015
2
Como passo de "começo da maior diferença positiva por tipos de objeto" para "descobrir o que faz com que esses objetos extras permaneçam na memória"? Eu vejo coisas muito gerais como int [], Objeto [], String, etc. Como faço para descobrir de onde elas vêm?
Vituel
48

Questionador aqui, devo dizer que a obtenção de uma ferramenta que não leva 5 minutos para responder a qualquer clique facilita muito a localização de possíveis vazamentos de memória.

Como as pessoas estão sugerindo várias ferramentas (eu só tentei o visual wm desde que obtive isso nos testes JDK e JProbe), acho que devo sugerir uma ferramenta de código aberto / livre criada na plataforma Eclipse, o Memory Analyzer (às vezes referido como memória SAP analisador) disponível em http://www.eclipse.org/mat/ .

O que é realmente interessante nessa ferramenta é que ela indexou o dump do heap quando o abri pela primeira vez, o que permitiu mostrar dados como heap retido sem esperar 5 minutos para cada objeto (praticamente todas as operações foram muito mais rápidas que as outras ferramentas que tentei) .

Quando você abre o dump, a primeira tela mostra um gráfico de pizza com os maiores objetos (contando a pilha retida) e é possível navegar rapidamente até os objetos que são grandes demais para maior conforto. Ele também possui um provável achado de vazamentos, que eu reconheço, que pode ser útil, mas como a navegação foi suficiente para mim, eu realmente não entrei nela.

jwiklund
fonte
1
Vale ressaltar: aparentemente no Java 5 e acima, o HeapDumpOnCtrlBreakparâmetro VM não está disponível . A solução que encontrei (até agora, ainda procurando) é usar o JMap para despejar o .hprofarquivo, que eu coloquei no Eclipse e use o MAT para examinar.
Ben Ben
1
Em relação à obtenção de despejo de heap, a maioria dos criadores de perfil (incluindo JVisualVM) inclui a opção de despejar heap e threads em um arquivo.
precisa saber é
13

Uma ferramenta é uma grande ajuda.

No entanto, há momentos em que você não pode usar uma ferramenta: o heap dump é tão grande que trava a ferramenta, você está tentando solucionar problemas de uma máquina em algum ambiente de produção ao qual você só tem acesso ao shell, etc.

Nesse caso, ajuda a conhecer o caminho do arquivo de despejo hprof.

Procure SITES BEGIN. Isso mostra quais objetos estão usando mais memória. Mas os objetos não são agrupados apenas por tipo: cada entrada também inclui um ID de "rastreamento". Você pode procurar esse "TRACE nnnn" para ver os primeiros quadros da pilha em que o objeto foi alocado. Freqüentemente, quando vejo onde o objeto está alocado, encontro um bug e pronto. Além disso, observe que você pode controlar quantos quadros são gravados na pilha com as opções para -Xrunhprof.

Se você verificar o site de alocação e não encontrar nada errado, precisará iniciar o encadeamento de alguns desses objetos ativos para objetos raiz, para encontrar a cadeia de referência inesperada. É aqui que uma ferramenta realmente ajuda, mas você pode fazer a mesma coisa manualmente (bem, com grep). Não há apenas um objeto raiz (ou seja, objeto não sujeito a coleta de lixo). Threads, classes e quadros de pilha agem como objetos raiz, e tudo o que eles referenciam fortemente não é colecionável.

Para fazer o encadeamento, procure na seção HEAP DUMP por entradas com o ID de rastreamento incorreto. Isso levará você a uma entrada OBJ ou ARR, que mostra um identificador de objeto exclusivo em hexadecimal. Pesquise todas as ocorrências desse ID para descobrir quem tem uma forte referência ao objeto. Siga cada um desses caminhos para trás enquanto se ramificam até descobrir onde está o vazamento. Veja por que uma ferramenta é tão útil?

Membros estáticos são reincidentes por vazamentos de memória. De fato, mesmo sem uma ferramenta, vale a pena gastar alguns minutos procurando no seu código por membros estáticos do Mapa. Um mapa pode aumentar? Alguma coisa limpa suas entradas?

erickson
fonte
“O despejo de heap é tão grande que trava a ferramenta” - verifiquei pela última vez jhate MATaparentemente tentei carregar todo o despejo de heap na memória e, portanto, geralmente trava com um OutOfMemoryErrordespejo grande (ou seja, dos aplicativos que mais precisam de análise de heap!) ) O NetBeans Profiler parece usar um algoritmo diferente para indexar referências, o que pode ficar lento em grandes lixões, mas pelo menos não consome memória ilimitada na ferramenta e trava.
precisa saber é
10

Na maioria das vezes, em aplicativos corporativos, o heap Java fornecido é maior que o tamanho ideal de no máximo 12 a 16 GB. Acho difícil fazer com que o criador de perfil do NetBeans funcione diretamente nesses grandes aplicativos java.

Mas geralmente isso não é necessário. Você pode usar o utilitário jmap que acompanha o jdk para obter um despejo de pilha "ativo", ou seja, o jmap despeja a pilha após executar o GC. Faça alguma operação no aplicativo, aguarde até que a operação seja concluída e faça outro despejo de pilha "ativo". Use ferramentas como o Eclipse MAT para carregar os heapdumps, classificar no histograma, ver quais objetos aumentaram ou quais são os mais altos. Isso daria uma pista.

su  proceeuser
/bin/jmap -dump:live,format=b,file=/tmp/2930javaheap.hrpof 2930(pid of process)

Há apenas um problema com essa abordagem; Enormes despejos de heap, mesmo com a opção ao vivo, podem ser grandes demais para serem transferidos para a volta do desenvolvimento e podem precisar de uma máquina com memória / RAM suficiente para abrir.

É aí que o histograma da classe entra em cena. Você pode despejar um histograma de classe ao vivo com a ferramenta jmap. Isso fornecerá apenas o histograma de classe do uso de memória. Basicamente, ele não terá informações para encadear a referência. Por exemplo, ele pode colocar a matriz de caracteres no topo. E classe String em algum lugar abaixo. Você tem que desenhar a conexão você mesmo.

jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt

Em vez de pegar dois lixões de pilha, use dois histogramas de classe, como descrito acima; Em seguida, compare os histogramas de classe e veja as classes que estão aumentando. Veja se você pode relacionar as classes Java às suas classes de aplicativos. Isso dará uma boa dica. Aqui está um script pythons que pode ajudá-lo a comparar dois dumps do histograma jmap. histogramparser.py

Finalmente, ferramentas como JConolse e VisualVm são essenciais para ver o crescimento da memória ao longo do tempo e para ver se há um vazamento de memória. Finalmente, às vezes o problema pode não ser um vazamento de memória, mas um alto uso de memória. Para isso, habilite o log do GC; use um GC de compactação mais avançado e mais novo, como G1GC; e você pode usar ferramentas jdk como jstat para ver o comportamento do GC ao vivo

jstat -gccause pid <optional time interval>

Outras referências ao google para -jhat, jmap, GC completo, alocação humongous, G1GC

Alex Punnen
fonte
1
adicionou uma publicação no blog com mais detalhes aqui - alexpunnen.blogspot.in/2015/06/…
Alex Punnen
5

Existem ferramentas que devem ajudá-lo a encontrar seu vazamento, como JProbe, YourKit, AD4J ou JRockit Mission Control. O último é o que eu pessoalmente conheço melhor. Qualquer boa ferramenta deve permitir aprofundar a um nível em que você possa identificar facilmente quais vazamentos e onde os objetos que estão vazando são alocados.

O uso de HashTables, Hashmaps ou similar é uma das poucas maneiras pelas quais você pode realmente vazar memória em Java. Se eu tivesse que encontrar o vazamento manualmente, imprimiria peridicamente o tamanho dos meus HashMaps e, a partir daí, encontraria o local em que eu adicionaria itens e esqueceria de excluí-los.

Tnilsson
fonte
4

Bem, sempre há a solução de baixa tecnologia de adicionar logs do tamanho de seus mapas quando você os modifica e, em seguida, pesquise nos logs quais mapas estão crescendo além de um tamanho razoável.

Mike Stone
fonte
1

O NetBeans possui um criador de perfil incorporado.

wbkang
fonte
0

Você realmente precisa usar um perfilador de memória que rastreie alocações. Dê uma olhada no JProfiler - o recurso "heap walker" é ótimo e eles têm integração com todos os principais Java IDEs. Não é gratuito, mas também não é tão caro (US $ 499 por uma única licença) - você gastará US $ 500 em um tempo muito rápido, lutando para encontrar um vazamento com ferramentas menos sofisticadas.

McKenzieG1
fonte
0

Você pode descobrir medindo o tamanho do uso da memória depois de chamar o coletor de lixo várias vezes:

Runtime runtime = Runtime.getRuntime();

while(true) {
    ...
    if(System.currentTimeMillis() % 4000 == 0){
        System.gc();
        float usage = (float) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
        System.out.println("Used memory: " + usage + "Mb");
    }

}

Se os números de saída forem iguais, não haverá vazamento de memória em seu aplicativo, mas se você notar uma diferença entre os números de uso de memória (números crescentes), haverá vazamento de memória em seu projeto. Por exemplo:

Used memory: 14.603279Mb
Used memory: 14.737213Mb
Used memory: 14.772224Mb
Used memory: 14.802681Mb
Used memory: 14.840599Mb
Used memory: 14.900841Mb
Used memory: 14.942261Mb
Used memory: 14.976143Mb

Observe que às vezes leva algum tempo para liberar memória por algumas ações como fluxos e soquetes. Você não deve julgar pelas primeiras saídas. Você deve testá-lo em um período específico de tempo.

Amir Fo
fonte
0

Confira este elenco de tela sobre como encontrar vazamentos de memória com o JProfiler. É uma explicação visual da resposta de @Dima Malenko.

Nota: Embora o JProfiler não seja um freeware, a versão de avaliação pode lidar com a situação atual.

Vaibhav Jain
fonte
0

Como a maioria de nós já usa o Eclipse para escrever código, por que não usar a Memory Analyzer Tool (MAT) no Eclipse. Isso funciona muito bem.

O Eclipse MAT é um conjunto de plug-ins para o IDE Eclipse, que fornece ferramentas para analisar a heap dumpspartir do aplicativo Java e identificar memory problemsno aplicativo.

Isso ajuda o desenvolvedor a encontrar vazamentos de memória com os seguintes recursos

  1. Adquirindo um instantâneo de memória (Heap Dump)
  2. Histograma
  3. Pilha Retida
  4. Árvore Dominator
  5. Explorando caminhos para as raízes do GC
  6. Inspetor
  7. Antipadrões de memória comum
  8. Linguagem de Consulta de Objetos

insira a descrição da imagem aqui

Sreeram Nair
fonte