Come trovare una perdita di memoria Java


142

Come si trova una perdita di memoria in Java (utilizzando, ad esempio, JHat)? Ho provato a caricare il dump dell'heap in JHat per dare un'occhiata di base. Tuttavia, non capisco come dovrei essere in grado di trovare il riferimento radice ( ref ) o come si chiama. Fondamentalmente, posso dire che ci sono diverse centinaia di megabyte di voci nella tabella hash ([java.util.HashMap $ Entry o qualcosa del genere), ma le mappe vengono utilizzate ovunque ... C'è un modo per cercare mappe di grandi dimensioni o forse trovare radici generali di alberi di grandi dimensioni?

[Modifica] Ok, ho letto le risposte finora, ma diciamo solo che sono un bastardo economico (il che significa che sono più interessato a imparare a usare JHat che a pagare per JProfiler). Inoltre, JHat è sempre disponibile poiché fa parte del JDK. A meno che ovviamente non ci sia modo con JHat se non la forza bruta, ma non posso credere che possa essere il caso.

Inoltre, non penso che sarò in grado di modificare effettivamente (aggiungendo la registrazione di tutte le dimensioni della mappa) ed eseguirlo per il tempo sufficiente a farmi notare la perdita.


Questo è un altro "voto" per JProfiler. Funziona abbastanza bene per l'analisi dell'heap, ha un'interfaccia utente decente e funziona abbastanza bene. Come dice McKenzieG1, $ 500 è più economico del tempo che altrimenti bruceresti cercando la fonte di queste perdite. Per quanto riguarda il prezzo degli strumenti, non è male.
giov

Risposte:


126

Uso il seguente approccio per trovare perdite di memoria in Java. Ho usato jProfiler con grande successo, ma credo che qualsiasi strumento specializzato con capacità grafiche (le differenze sono più facili da analizzare in forma grafica) funzionerà.

  1. Avviare l'applicazione e attendere fino allo stato "stabile", quando tutta l'inizializzazione è completa e l'applicazione è inattiva.
  2. Eseguire l'operazione sospettata di produrre più volte una perdita di memoria per consentire l'esecuzione di qualsiasi inizializzazione correlata alla cache.
  3. Esegui GC e scatta un'istantanea della memoria.
  4. Eseguire di nuovo l'operazione. A seconda della complessità dell'operazione e delle dimensioni dei dati elaborati, potrebbe essere necessario eseguire più o più volte l'operazione.
  5. Esegui GC e scatta un'istantanea della memoria.
  6. Esegui un diff per 2 istantanee e analizzalo.

Fondamentalmente l'analisi dovrebbe iniziare dalla più grande differenza positiva, per esempio, dai tipi di oggetti e trovare ciò che fa sì che quegli oggetti extra rimangano nella memoria.

Per le applicazioni Web che elaborano le richieste in più thread, l'analisi diventa più complicata, ma si applica comunque un approccio generale.

Ho fatto un certo numero di progetti specificamente mirati a ridurre il footprint di memoria delle applicazioni e questo approccio generale con alcune modifiche e trucchi specifici dell'applicazione ha sempre funzionato bene.


7
La maggior parte (se non tutti) i profiler Java forniscono un'opzione per invocare GC con un clic di un pulsante. Oppure puoi chiamare System.gc () dalla posizione appropriata nel tuo codice.
Dima Malenko,

3
Anche se chiamiamo System.gc () JVM potrebbe scegliere di trascurare la chiamata. AFAIK questo è specifico per JVM. +1 alla risposta.
Aniket Thakur,

4
Che cosa è esattamente una "istantanea della memoria?" C'è qualcosa che mi dirà il numero di ogni tipo di oggetto che il mio codice è in esecuzione?
Gnomed

2
Come posso passare da "iniziare dalla massima differenza positiva per tipo di oggetto" a "trovare cosa fa sì che quegli oggetti extra rimangano nella memoria"? Vedo cose molto generali come int [], Object [], String, ecc. Come trovo da dove vengono?
Vituel,

48

Interrogatore qui, devo dire che ottenere uno strumento che non richiede 5 minuti per rispondere a qualsiasi clic rende molto più facile trovare potenziali perdite di memoria.

Dato che le persone stanno suggerendo diversi strumenti (ho provato solo Visual WM da quando l'ho ottenuto nella versione di prova JDK e JProbe), anche se dovrei suggerire uno strumento gratuito / open source basato sulla piattaforma Eclipse, Memory Analyzer (a volte indicato come memoria SAP analizzatore) disponibile su http://www.eclipse.org/mat/ .

La cosa davvero interessante di questo strumento è che ha indicizzato il dump dell'heap quando l'ho aperto per la prima volta, il che gli ha permesso di mostrare dati come l'heap trattenuto senza aspettare 5 minuti per ogni oggetto (praticamente tutte le operazioni sono state molto più veloci degli altri strumenti che ho provato) .

Quando apri il dump, la prima schermata mostra un grafico a torta con gli oggetti più grandi (contando l'heap conservato) e puoi spostarti rapidamente verso gli oggetti troppo grandi per il massimo comfort. Ha anche una probabile perdita di sospetti che riconosco possa tornare utile, ma poiché la navigazione è stata sufficiente per me, non ci sono riuscito.


1
Vale la pena notare: apparentemente in Java 5 e versioni successive, il HeapDumpOnCtrlBreakparametro VM non è disponibile . La soluzione che ho trovato (finora, ancora alla ricerca) è usare JMap per scaricare il .hproffile, che poi inserisco in Eclipse e uso MAT per esaminare.
Ben

1
Per quanto riguarda l'ottenimento del dump dell'heap, la maggior parte dei profiler (incluso JVisualVM) include l'opzione per scaricare sia l'heap che i thread in un file.
bbaja42,

13

Uno strumento è di grande aiuto.

Tuttavia, ci sono momenti in cui non è possibile utilizzare uno strumento: il dump dell'heap è così enorme che si arresta in modo anomalo allo strumento, si sta tentando di risolvere un problema in un ambiente di produzione a cui si ha solo l'accesso alla shell, ecc.

In tal caso, aiuta a orientarsi nel file di dump hprof.

Cerca SITI INIZIA. Questo ti mostra quali oggetti stanno usando più memoria. Ma gli oggetti non sono raggruppati solo per tipo: ogni voce include anche un ID "traccia". È quindi possibile cercare quella "TRACE nnnn" per vedere i primi frame dello stack in cui è stato allocato l'oggetto. Spesso, quando vedo dove viene allocato l'oggetto, trovo un bug e ho finito. Si noti inoltre che è possibile controllare il numero di frame registrati nello stack con le opzioni su -Xrunhprof.

Se si controlla il sito di allocazione e non si vede nulla di sbagliato, è necessario iniziare il concatenamento all'indietro da alcuni di questi oggetti attivi a oggetti radice, per trovare la catena di riferimento imprevista. Qui è dove uno strumento aiuta davvero, ma puoi fare la stessa cosa a mano (beh, con grep). Non esiste un solo oggetto radice (ovvero oggetto non soggetto a garbage collection). Thread, classi e frame stack fungono da oggetti root e tutto ciò a cui fanno riferimento fortemente non è collezionabile.

Per eseguire il concatenamento, cercare nella sezione HEAP DUMP le voci con ID traccia non valido. Questo ti porterà a una voce OBJ o ARR, che mostra un identificatore univoco dell'oggetto in esadecimale. Cerca tutte le occorrenze di quell'ID per trovare chi ha un forte riferimento all'oggetto. Segui ciascuno di questi percorsi all'indietro mentre si diramano fino a capire dove si trova la perdita. Vedi perché uno strumento è così utile?

I membri statici sono recidivi per perdite di memoria. In effetti, anche senza uno strumento, varrebbe la pena dedicare qualche minuto alla ricerca del codice per i membri statici della mappa. Una mappa può ingrandirsi? Qualcosa ha mai ripulito le sue voci?


"Il dump dell'heap è così enorme da causare l' arresto anomalo dello strumento": ultimamente ho verificato jhate MATapparentemente ho provato a caricare l'intero dump dell'heap in memoria, e quindi in genere si arresta in modo anomalo con un OutOfMemoryErrordump di grandi dimensioni (ad esempio, dalle applicazioni che hanno maggiormente bisogno dell'analisi dell'heap! ). Il profiler NetBeans sembra utilizzare un algoritmo diverso per l'indicizzazione dei riferimenti che può rallentare in grandi dump ma almeno non consuma memoria illimitata nello strumento e si arresta in modo anomalo.
Jesse Glick,

10

Il più delle volte, nelle applicazioni aziendali l'heap Java fornito è più grande della dimensione ideale di max 12-16 GB. Ho trovato difficile far funzionare il profiler NetBeans direttamente su queste grandi app java.

Ma di solito questo non è necessario. È possibile utilizzare l'utilità jmap fornita con jdk per eseguire un dump dell'heap "live", ovvero jmap eseguirà il dump dell'heap dopo l'esecuzione di GC. Eseguire alcune operazioni sull'applicazione, attendere fino al completamento dell'operazione, quindi eseguire un altro dump dell'heap "live". Usa strumenti come Eclipse MAT per caricare gli heapdump, ordinare l'istogramma, vedere quali oggetti sono aumentati o quali sono i più alti. Questo darebbe un indizio.

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

C'è solo un problema con questo approccio; Enormi dump di heap, anche con l'opzione live, potrebbero essere troppo grandi per essere trasferiti al giro di sviluppo e potrebbero aver bisogno di una macchina con memoria / RAM sufficiente per aprire.

È qui che entra in scena l'istogramma della classe. È possibile scaricare un istogramma di classe live con lo strumento jmap. Ciò fornirà solo l'istogramma di classe dell'utilizzo della memoria, in pratica non avrà le informazioni per concatenare il riferimento. Ad esempio, potrebbe mettere l'array char nella parte superiore. E la classe String da qualche parte in basso. Devi disegnare tu stesso la connessione.

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

Invece di prendere due dump dell'heap, prendi due istogrammi di classe, come descritto sopra; Quindi confrontare gli istogrammi di classe e vedere le classi che stanno aumentando. Vedi se riesci a mettere in relazione le classi Java con le tue classi applicative. Questo darà un suggerimento abbastanza buono. Ecco uno script Pythons che può aiutarti a confrontare due dump dell'istogramma jmap. histogramparser.py

Infine, strumenti come JConolse e VisualVm sono essenziali per vedere la crescita della memoria nel tempo e vedere se c'è una perdita di memoria. Infine, a volte il problema potrebbe non essere una perdita di memoria, ma un elevato utilizzo della memoria. Per questo abilitare la registrazione GC; utilizzare un GC di compattazione più avanzato e nuovo come G1GC; e puoi usare strumenti jdk come jstat per vedere dal vivo il comportamento di GC

jstat -gccause pid <optional time interval>

Altri riferimenti a google per -jhat, jmap, GC completo, allocazione Humongous, G1GC


1
aggiunto un post sul blog con maggiori dettagli qui - alexpunnen.blogspot.in/2015/06/…
Alex Punnen

5

Ci sono strumenti che dovrebbero aiutarti a trovare la tua perdita, come JProbe, YourKit, AD4J o JRockit Mission Control. L'ultimo è quello che conosco meglio di persona. Qualsiasi buon strumento dovrebbe consentire di eseguire il drill-down a un livello in cui è possibile identificare facilmente quali perdite e dove sono allocati gli oggetti che perdono.

L'uso di HashTable, Hashmap o simili è uno dei pochi modi in cui è possibile perdere la memoria in modo reale in Java. Se dovessi trovare la perdita a mano, stamperei peridamente la dimensione delle mie HashMap, e da lì trovo quella in cui aggiungo elementi e dimentico di eliminarli.


4

Bene, c'è sempre la soluzione a bassa tecnologia di aggiungere la registrazione della dimensione delle tue mappe quando le modifichi, quindi cercare i registri per cui le mappe stanno crescendo oltre una dimensione ragionevole.



0

Devi davvero usare un profiler di memoria che tiene traccia delle allocazioni. Dai un'occhiata a JProfiler : la loro funzione "heap walker" è eccezionale e hanno l'integrazione con tutti i principali IDE Java. Non è gratuito, ma non è nemmeno così costoso ($ 499 per una singola licenza): brucerai $ 500 di tempo abbastanza rapidamente lottando per trovare una perdita con strumenti meno sofisticati.


0

Puoi scoprirlo misurando le dimensioni di utilizzo della memoria dopo aver chiamato più volte garbage collector:

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 i numeri di output erano uguali, non vi è alcuna perdita di memoria nell'applicazione, ma se si riscontra una differenza tra i numeri di utilizzo della memoria (numeri crescenti), si verifica una perdita di memoria nel progetto. Per esempio:

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

Si noti che a volte ci vuole del tempo per liberare memoria da alcune azioni come stream e socket. Non dovresti giudicare dalle prime uscite, dovresti testarlo in un determinato periodo di tempo.


0

Dai un'occhiata a questo cast di schermate sulla ricerca di perdite di memoria con JProfiler. È una spiegazione visiva della risposta di @Dima Malenko.

Nota: sebbene JProfiler non sia freeware, la versione di prova può gestire la situazione attuale.


0

Poiché la maggior parte di noi utilizza già Eclipse per la scrittura di codice, perché non utilizzare Memory Analyzer Tool (MAT) in Eclipse. Funziona benissimo.

L' Eclipse MAT è un insieme di plug-in per l'IDE Eclipse che fornisce strumenti per analizzare heap dumpsdall'applicazione Java e per identificare memory problemsl'applicazione.

Questo aiuta lo sviluppatore a trovare perdite di memoria con le seguenti funzionalità

  1. Acquisizione di un'istantanea della memoria (Heap Dump)
  2. Istogramma
  3. Mucchio trattenuto
  4. Albero dei dominatori
  5. Esplorazione di percorsi verso le radici GC
  6. Ispettore
  7. Anti-Pattern di memoria comune
  8. Linguaggio query oggetto

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.