So finden Sie einen Java-Speicherverlust


142

Wie finden Sie einen Speicherverlust in Java (z. B. mit JHat)? Ich habe versucht, den Heap-Dump in JHat zu laden, um einen grundlegenden Überblick zu erhalten. Ich verstehe jedoch nicht, wie ich die Stammreferenz ( ref ) oder wie auch immer sie heißen soll finden soll. Grundsätzlich kann ich feststellen, dass es mehrere hundert Megabyte an Hash-Tabelleneinträgen gibt ([java.util.HashMap $ Entry oder ähnliches), aber Karten werden überall verwendet ... Gibt es eine Möglichkeit, nach großen Karten zu suchen? , oder vielleicht allgemeine Wurzeln großer Objektbäume finden?

[Bearbeiten] Ok, ich habe die Antworten bisher gelesen, aber sagen wir einfach, ich bin ein billiger Bastard (was bedeutet, dass ich mehr daran interessiert bin, den Umgang mit JHat zu lernen, als für JProfiler zu bezahlen). Außerdem ist JHat immer verfügbar, da es Teil des JDK ist. Es sei denn natürlich, es gibt keinen Weg mit JHat außer roher Gewalt, aber ich kann nicht glauben, dass dies der Fall sein kann.

Ich glaube auch nicht, dass ich tatsächlich Änderungen vornehmen (Protokollierung aller Kartengrößen hinzufügen ) und es so lange ausführen kann, bis ich das Leck bemerke.


Dies ist eine weitere "Abstimmung" für JProfiler. Es funktioniert ziemlich gut für die Heap-Analyse, hat eine anständige Benutzeroberfläche und funktioniert ziemlich gut. Wie McKenzieG1 sagt, sind 500 US-Dollar billiger als die Zeit, die Sie sonst verbrennen würden, um nach der Quelle für diese Lecks zu suchen. Was den Preis von Werkzeugen angeht, ist es nicht schlecht.
Joev

Antworten:


126

Ich verwende den folgenden Ansatz, um Speicherlecks in Java zu finden. Ich habe jProfiler mit großem Erfolg verwendet, aber ich glaube, dass jedes spezielle Tool mit Grafikfunktionen (Unterschiede sind in grafischer Form einfacher zu analysieren) funktioniert.

  1. Starten Sie die Anwendung und warten Sie, bis sie den Status "stabil" erreicht hat, wenn die gesamte Initialisierung abgeschlossen ist und die Anwendung inaktiv ist.
  2. Führen Sie den Vorgang, bei dem der Verdacht besteht, dass er einen Speicherverlust verursacht, mehrmals aus, damit eine DB-bezogene Cache-Initialisierung stattfinden kann.
  3. Führen Sie GC aus und machen Sie einen Speicher-Snapshot.
  4. Führen Sie den Vorgang erneut aus. Abhängig von der Komplexität des Vorgangs und der Größe der Daten, die verarbeitet werden, muss der Vorgang möglicherweise mehrere bis viele Male ausgeführt werden.
  5. Führen Sie GC aus und machen Sie einen Speicher-Snapshot.
  6. Führen Sie ein Diff für 2 Schnappschüsse aus und analysieren Sie es.

Grundsätzlich sollte die Analyse vom größten positiven Unterschied beispielsweise nach Objekttypen ausgehen und herausfinden, warum diese zusätzlichen Objekte im Speicher bleiben.

Für Webanwendungen, die Anforderungen in mehreren Threads verarbeiten, wird die Analyse komplizierter, es gilt jedoch weiterhin ein allgemeiner Ansatz.

Ich habe eine ganze Reihe von Projekten durchgeführt, die speziell darauf abzielen, den Speicherbedarf der Anwendungen zu verringern, und dieser allgemeine Ansatz mit einigen anwendungsspezifischen Optimierungen und Tricks hat immer gut funktioniert.


7
Die meisten (wenn nicht alle) Java-Profiler bieten Ihnen die Möglichkeit, GC mit einem Klick auf eine Schaltfläche aufzurufen. Oder Sie können System.gc () an einer geeigneten Stelle in Ihrem Code aufrufen.
Dima Malenko

3
Selbst wenn wir System.gc () aufrufen, kann JVM den Aufruf vernachlässigen. AFAIK dies ist JVM-spezifisch. +1 auf die Antwort.
Aniket Thakur

4
Was genau ist ein "Speicher-Schnappschuss"? Gibt es etwas, das mir die Nummer jedes Objekttyps sagt, auf dem mein Code ausgeführt wird?
Gnomed

2
Wie gehe ich von "Ausgehend vom größten positiven Unterschied nach Objekttypen" zu "Finden, was bewirkt, dass diese zusätzlichen Objekte im Speicher bleiben"? Ich sehe sehr allgemeine Dinge wie int [], Object [], String usw. Wie finde ich, woher sie kommen?
Vituel

48

Fragender hier, ich muss sagen, dass das Erhalten eines Tools, das keine 5 Minuten benötigt, um einen Klick zu beantworten, das Auffinden potenzieller Speicherlecks erheblich erleichtert.

Da die Leute mehrere Tools vorschlagen (ich habe Visual Wm nur ausprobiert, seit ich das in der JDK- und JProbe-Testversion erhalten habe), sollte ich ein Free / Open Source-Tool vorschlagen, das auf der Eclipse-Plattform basiert, den Memory Analyzer (manchmal auch als SAP-Speicher bezeichnet) Analysator) verfügbar unter http://www.eclipse.org/mat/ .

Was an diesem Tool wirklich cool ist, ist, dass es den Heap-Dump beim ersten Öffnen indiziert hat, sodass Daten wie beibehaltener Heap angezeigt werden konnten, ohne 5 Minuten auf jedes Objekt zu warten (so gut wie alle Vorgänge waren tonnenweise schneller als die anderen Tools, die ich ausprobiert habe). .

Wenn Sie den Speicherauszug öffnen, wird auf dem ersten Bildschirm ein Kreisdiagramm mit den größten Objekten angezeigt (wobei der verbleibende Heap gezählt wird), und Sie können schnell zu den Objekten navigieren, die für den Komfort zu groß sind. Es hat auch einen Find wahrscheinlichen Leckverdächtigen, den ich als nützlich erachten kann, aber da die Navigation für mich ausreichte, bin ich nicht wirklich darauf gekommen.


1
Bemerkenswert: Anscheinend ist der Java- HeapDumpOnCtrlBreakParameter in Java 5 und höher nicht verfügbar . Die Lösung, die ich gefunden habe (bisher noch gesucht), besteht darin, JMap zum Speichern der .hprofDatei zu verwenden, die ich dann in Eclipse eingefügt und mit MAT untersucht habe.
Ben

1
In Bezug auf das Abrufen eines Heap-Dumps bieten die meisten Profiler (einschließlich JVisualVM) die Option, sowohl Heap als auch Threads in eine Datei zu sichern.
bbaja42

13

Ein Werkzeug ist eine große Hilfe.

Es gibt jedoch Situationen, in denen Sie kein Tool verwenden können: Der Heap-Dump ist so groß, dass das Tool abstürzt. Sie versuchen, eine Maschine in einer Produktionsumgebung zu beheben, auf die Sie nur Shell-Zugriff haben usw.

In diesem Fall ist es hilfreich, sich in der hprof-Dump-Datei zurechtzufinden.

Suchen Sie nach SITES BEGIN. Dies zeigt Ihnen, welche Objekte den meisten Speicher belegen. Die Objekte werden jedoch nicht nur nach Typ zusammengefasst: Jeder Eintrag enthält auch eine "Trace" -ID. Sie können dann nach "TRACE nnnn" suchen, um die obersten Frames des Stapels anzuzeigen, in dem das Objekt zugewiesen wurde. Sobald ich sehe, wo das Objekt zugeordnet ist, finde ich oft einen Fehler und bin fertig. Beachten Sie außerdem, dass Sie mit den Optionen -Xrunhprof steuern können, wie viele Frames im Stapel aufgezeichnet werden.

Wenn Sie die Zuordnungssite auschecken und keine Fehler feststellen, müssen Sie von einigen dieser Live-Objekte mit Stammobjekten rückwärts verketten, um die unerwartete Referenzkette zu finden. Hier hilft ein Tool wirklich, aber Sie können das Gleiche von Hand tun (na ja, mit grep). Es gibt nicht nur ein Stammobjekt (dh ein Objekt, das keiner Speicherbereinigung unterliegt). Threads, Klassen und Stapelrahmen fungieren als Stammobjekte, und alles, worauf sie stark verweisen, kann nicht gesammelt werden.

Um die Verkettung durchzuführen, suchen Sie im Abschnitt HEAP DUMP nach Einträgen mit der falschen Trace-ID. Dadurch gelangen Sie zu einem OBJ- oder ARR-Eintrag, in dem eine eindeutige Objektkennung hexadezimal angezeigt wird. Suchen Sie nach allen Vorkommen dieser ID, um herauszufinden, wer einen starken Bezug zum Objekt hat. Folgen Sie jedem dieser Pfade rückwärts, während Sie sich verzweigen, bis Sie herausfinden, wo das Leck ist. Sehen Sie, warum ein Werkzeug so praktisch ist?

Statische Mitglieder sind ein Wiederholungstäter für Speicherlecks. Selbst ohne ein Tool lohnt es sich, ein paar Minuten in Ihrem Code nach statischen Map-Mitgliedern zu suchen. Kann eine Karte groß werden? Bereinigt irgendetwas jemals seine Einträge?


„Die Heapdump ist so riesig es das Tool abstürzt“ -letzte habe ich geprüft, jhatund MAToffenbar versuchen , die ganze Heapdump in dem Speicher zu laden, und so typischerweise mit einem Absturz OutOfMemoryErrorauf große Deponien (dh von den Anwendungen , die die meisten Heap - Analyse benötigt! ). Der NetBeans Profiler scheint einen anderen Algorithmus zum Indizieren von Referenzen zu verwenden, der bei großen Speicherauszügen langsam werden kann, aber zumindest keinen unbegrenzten Speicher im Tool verbraucht und abstürzt.
Jesse Glick

10

In Unternehmensanwendungen ist der angegebene Java-Heap meistens größer als die ideale Größe von maximal 12 bis 16 GB. Ich habe es schwierig gefunden, den NetBeans-Profiler direkt auf diesen großen Java-Apps zum Laufen zu bringen.

Aber normalerweise wird dies nicht benötigt. Sie können das mit dem jdk gelieferte Dienstprogramm jmap verwenden, um einen "Live" -Heap-Dump zu erstellen. Das heißt, jmap gibt den Heap nach dem Ausführen von GC aus. Führen Sie eine Operation für die Anwendung aus, warten Sie, bis die Operation abgeschlossen ist, und führen Sie dann einen weiteren "Live" -Heap-Dump durch. Verwenden Sie Tools wie Eclipse MAT, um die Heapdumps zu laden, das Histogramm zu sortieren und festzustellen, welche Objekte zugenommen haben oder welche am höchsten sind. Dies würde einen Hinweis geben.

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

Bei diesem Ansatz gibt es nur ein Problem. Riesige Heap-Dumps sind selbst mit der Live-Option möglicherweise zu groß, um in die Entwicklungsrunde übertragen zu werden, und benötigen möglicherweise einen Computer mit genügend Speicher / RAM zum Öffnen.

Hier kommt das Klassenhistogramm ins Spiel. Sie können ein Live-Klassenhistogramm mit dem jmap-Tool sichern. Dies gibt nur das Klassenhistogramm der Speichernutzung an. Grundsätzlich verfügt es nicht über die Informationen, um die Referenz zu verketten. Zum Beispiel kann es ein char-Array an die Spitze setzen. Und String-Klasse irgendwo unten. Sie müssen die Verbindung selbst herstellen.

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

Anstatt zwei Heap-Dumps zu erstellen, nehmen Sie zwei Klassenhistogramme wie oben beschrieben. Vergleichen Sie dann die Klassenhistogramme und sehen Sie die Klassen, die zunehmen. Überprüfen Sie, ob Sie die Java-Klassen mit Ihren Anwendungsklassen verknüpfen können. Dies wird einen ziemlich guten Hinweis geben. Hier ist ein Python-Skript, mit dem Sie zwei jmap-Histogramm-Dumps vergleichen können. histogramparser.py

Schließlich sind Tools wie JConolse und VisualVm unerlässlich, um das Speicherwachstum im Laufe der Zeit zu beobachten und festzustellen, ob ein Speicherverlust vorliegt. Schließlich ist Ihr Problem manchmal nicht ein Speicherverlust, sondern eine hohe Speichernutzung. Verwenden Sie dazu die GC-Protokollierung. Verwenden Sie einen fortschrittlicheren und neueren Komprimierungs-GC wie G1GC. und Sie können JDK-Tools wie jstat verwenden, um das GC-Verhalten live zu sehen

jstat -gccause pid <optional time interval>

Andere Referenzen zu googeln für -jhat, jmap, Full GC, Humongous Allocation, G1GC


1
hat einen Blog-Beitrag mit weiteren Details hier hinzugefügt
Alex Punnen

5

Es gibt Tools wie JProbe, YourKit, AD4J oder JRockit Mission Control, die Ihnen beim Auffinden Ihres Lecks helfen sollen. Der letzte ist der, den ich persönlich am besten kenne. Mit jedem guten Werkzeug sollten Sie einen Drilldown bis zu einer Ebene durchführen können, auf der Sie leicht erkennen können, welche Lecks vorhanden sind und wo die undichten Objekte zugeordnet sind.

Die Verwendung von HashTables, Hashmaps oder ähnlichem ist eine der wenigen Möglichkeiten, mit denen Sie tatsächlich Speicher in Java verlieren können. Wenn ich das Leck von Hand finden müsste, würde ich die Größe meiner HashMaps peridisch drucken und von dort diejenige finden, bei der ich Elemente hinzufüge und vergesse, sie zu löschen.


4

Nun, es gibt immer die Low-Tech-Lösung, die Protokollierung der Größe Ihrer Karten hinzuzufügen, wenn Sie sie ändern, und dann die Protokolle zu durchsuchen, nach denen Karten über eine vernünftige Größe hinauswachsen.


1

NetBeans verfügt über einen integrierten Profiler.


0

Sie müssen wirklich einen Speicherprofiler verwenden, der die Zuordnungen verfolgt. Schauen Sie sich JProfiler an - die Funktion "Heap Walker" ist großartig und sie sind in alle wichtigen Java-IDEs integriert. Es ist nicht kostenlos, aber auch nicht so teuer (499 US-Dollar für eine Einzellizenz) - Sie werden ziemlich schnell Zeit im Wert von 500 US-Dollar verbrauchen, wenn Sie mit weniger ausgefeilten Tools Schwierigkeiten haben, ein Leck zu finden.


0

Sie können dies herausfinden, indem Sie die Größe der Speichernutzung messen, nachdem Sie Garbage Collector mehrmals aufgerufen haben:

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");
    }

}

Wenn die Ausgabenummern gleich waren, liegt in Ihrer Anwendung kein Speicherverlust vor. Wenn Sie jedoch einen Unterschied zwischen der Anzahl der Speichernutzungen festgestellt haben (zunehmende Anzahl), liegt in Ihrem Projekt ein Speicherverlust vor. Beispielsweise:

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

Beachten Sie, dass es manchmal einige Zeit dauert, Speicher durch bestimmte Aktionen wie Streams und Sockets freizugeben. Sie sollten nicht nach den ersten Ausgaben urteilen, sondern sie in einer bestimmten Zeit testen.


0

Testen Sie diesen Bildschirm, um Speicherlecks mit JProfiler zu finden. Es ist eine visuelle Erklärung von @Dima Malenko Answer.

Hinweis: Obwohl JProfiler keine Freeware ist, kann die Testversion die aktuelle Situation bewältigen.


0

Da die meisten von uns Eclipse bereits zum Schreiben von Code verwenden, sollten Sie das Memory Analyzer Tool (MAT) in Eclipse verwenden. Es funktioniert großartig.

Die Eclipse MAT ist eine Reihe von Plug-Ins für die Eclipse-IDE, die Tools zum Analysieren heap dumpsaus einer Java-Anwendung und zum Identifizieren memory problemsin der Anwendung bereitstellen.

Dies hilft dem Entwickler, Speicherlecks mit den folgenden Funktionen zu finden

  1. Erfassen eines Speicher-Snapshots (Heap Dump)
  2. Histogramm
  3. Zurückbehaltener Haufen
  4. Dominatorbaum
  5. Erkundung von Pfaden zu den GC-Wurzeln
  6. Inspektor
  7. Allgemeine Speicher-Anti-Muster
  8. Objektabfragesprache

Geben Sie hier die Bildbeschreibung ein

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.