Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
CONTACT 
Effective Java - Post Mortem Heap Dump Analysis

Effective Java - Post Mortem Heap Dump Analysis  
Memory Leaks
Heap Dump Analyse
 

Java Magazin, April 2013
Klaus Kreft & Angelika Langer

Dies ist die Überarbeitung eines Manuskripts für einen Artikel, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

Im vorangegangenen Beitrag unserer Reihe über Memory Leaks in Java haben wir uns angesehen, wie man mit Hilfe von Profilern nach Memory Leaks suchen kann.  Dieses Mal wollen wir diskutieren, wie man in Heap Dumps Memory Leaks aufspüren kann.
 
 

Im vorangegangenen Beitrag haben wir eine dynamische Memory-Leak-Analyse betrachtet, bei der wir uns mit einem Profiler an eine laufende Anwendung gehängt, speicherneutrale Use Cases beobachtet und nach Objekten gesucht haben, die die Use Cases überleben, obwohl sie eigentlich nur für die Verarbeitung gebraucht wurden und hinterher hätten verschwunden sein sollten.
 
 

Dieses Mal wollen wir uns die Post-Mortem-Analyse ansehen und wie man sie durchführt, nachdem die Anwendung sich bereits (wegen Speichermangels) beendet hat. Für diese Analyse steht in der Regel wenig Information zur Verfügung. Oft ist es nur der Heap Dump, den die JVM beim Abbruch erzeugt hat. 
 
 

Wie beim letzten Artikel wollen wir wieder das Beispiel aus unserem ersten Artikel der Reihe über Memory Leaks aufgreifen (siehe / MEMLKS-1 /).  Dort hatten wir ein kurzes, aber fehlerhaftes Programm mit Memory Leak angesehen.  Es ging um die Implementierung eines rudimentären Servers auf Basis der mit Java 7 eingeführten  AsynchronousSocketChannel s.  Wir hatten pro Client einen Eintrag in einer Map gemacht, den wir aber am Ende der Client-Session nicht wieder gelöscht haben.  Infolgedessen wächst die Map stetig an.  Dies führt bei unserem Server zunächst zu Speicherengpässen mit auffallend langen Stop-the-World-Pausen des Garbage Collectors und am Ende zum Absturz mit  OutOfMemoryError .  Wie findet man nun ein solches Memory Leak, wenn man nichts weiter als einen Heap Dump zur Verfügung hat?
 
 

Post-Mortem-Memory-Leak-Analyse

Nehmen wir also an, unser Server ist mit der JVM-Option  -XX:+HeapDumpOnOutOfMemoryError gestartet worden; dann wird der Heap Dump im Falle der JVM-Terminierung wegen  OutOfMemoryError automatisch erzeugt.  Man kann Heap Dumps auch anders erzeugen; siehe dazu die Box „Wie bekomme ich einen Heap Dump?“ 

 
 

Ein gutes Werkzeug für die Analyse von Heap Dumps ist der kostenlose Memory Analyzer MAT (siehe / MAT /).  Er hat gegenüber anderen Werkzeugen den Vorteil, dass er auch sehr große Heap Dumps verarbeiten kann.   Das schafft er, weil er den Heap Dump zuallererst einmal indiziert, damit er hinterher in vertretbarer Zeit und mit verkraftbarem Speicherverbrauch eine Analyse auf dem Dump ermöglichen kann.
 
 

Dominatoren

MAT analysiert die Referenzbeziehungen zwischen den Objekten auf dem Heap und erzeugt daraus einen sogenannten Dominator-Baum (engl. dominator tree ).  Im Dominator-Baum sind die Verweise zwischen den Objekten so arrangiert, dass die Verweise von einem dominierenden Knoten zu den dominierten Knoten verlaufen.  Dabei ist ein dominierender Knoten, d.h. der Dominator, derjenige Knoten, den man im „normalen“ Referenzgrafen passieren muss, um die dominierten Knoten zu erreichen.    Das kann man schön am Beispiel einer doppelt-verketteten Liste sehen.
 
 
„normale“ Referenz-Beziehungen
Dominator-Baum
Die „normalen“ Referenzbeziehungen sind relativ kompliziert, denn von jedem Knoten in der Liste gehen drei weitere Verweise aus: vorwärts zum nächsten Knoten, rückwärts zum vorangegangen Knoten und ein Verweis auf die Nutzdaten im Knoten.  Meistens sind die Knoten einer doppelt-verketteten Liste auch noch zyklisch verknüpft (d.h. der letzte Knoten verweist wieder auf den ersten), so dass es beim Betrachten der einzelnen Listenknoten und ihrer Verweisbeziehungen nicht ohne weiteres zu erkennen ist, wer wen am Leben erhält.

 
 

Im Dominator-Baum sind die Knoten anders arrangiert. Um von dem Objekt, das auf die Liste verweist (Knoten ganz oben) zu einem der Listenelemente zu gelangen, muss man stets den ersten Knoten in der Liste passieren.  Dieser Eintrittsknoten dominiert deshalb alle anderen Listenknoten und steht im Dominator-Baum über den anderen Listenknoten.  Wenn man nun für jeden Knoten im Dominator-Baum berechnet, wie viel Speicher an dem Knoten dranhängt, d.h. an allen Referenzen, die von diesem Knoten ausgehen, dann sieht man sehr gut die Akkumulierungspunkte.  Im obigen Beispiel ist es die eine Referenz vom obersten Objekt, die auf den Eintrittsknoten der Liste zeigt, die die gesamte Liste und alle ihre Elemente am Leben erhält.
 
 

Der Memory Analyzer MAT baut aus den Referenzen, die er im Heap Dump findet, einen solchen Dominator-Baum für alle Java Objekte auf, sucht die Akkumulierungspunkte heraus und bietet diese als „leak suspects“ für die Analyse an.  Mit anderen Worten, MAT sucht aus den Objekten auf dem Heap diejenigen heraus, die große Mengen Speicher am Leben erhalten.  Für unser Beispiel sieht der Suspect Report so aus:
 
 


 
 

In unserem Beispiel wird ein HashMap-Segment als Hauptverdächtiger präsentiert.  In der Tat, an dem verdächtigen Objekt hängt knapp die Hälfte des verbrauchten Speichers der Anwendung.  Man schaut sich dann an, was die verdächtige Map enthält und wer die Map referenziert.  Auch diese Information ist im MAT gut aufbereitet:
 
 


 
 

Man sieht sofort, dass es sich bei der verdächtigen Map um die  ConcurrentHashMap namens  segments im Server handelt, die wir auch mittels der dynamischen Analyse im letzten Artikel gefunden hatten.
 
 

Noch ein paar Tipps zu Memory-Leak- Analyse
 
 

In der Realität ist es u.U. etwas schwieriger, das Memory Leak zu identifizieren.  Beispielweise findet man das Leak nicht so leicht, wenn man die Heap Dumps schon relativ früh zieht, d.h. nicht erst beim  OutOfMemoryError .  Dann kann es  vorkommen, dass die Map noch vergleichsweise klein ist und nicht verdächtiger Dominator identifiziert wird.  In unserem Beispiel war die Finalizer-Queue anfangs deutlich größer war als unsere Map, die das eigentliche Problem darstellt.  Man muss dem Leak also Zeit geben, als Dominator sichtbar zu werden. 
 
 

Im Übrigen sei noch einmal darauf hingewiesen, dass nicht jedes Programm, das wegen einem  OutOfMemoryError abstürzt, automatisch immer ein Memory Leak haben muss.  Manche Anwendungen brauchen einfach mehr Speicher, als ihnen zur Verfügung gestellt wurde. Ob der Absturz durch ein Memory Leak verursacht wurde, kann man herausfinden, indem man der Anwendung mehr Speicher mit  –Xmx<size> zuteilt.  Wenn sie dann immer noch abstürzt, schaut man sich die Dominatoren in den OutOfMemory-Heap-Dumps an.  Ein Dominator, der bei mehr Speicher noch größer ist als vorher bei weniger Speicher, ist unter Umständen ein Hinweis auf ein Memory Leak.  
 
 

Ansonsten hilft es, wenn man den Text liest, der im  OutOfMemoryError enthalten ist.  Nicht immer heißt es:  java.lang.OutOfMemoryError:  Java heap space “.  Texte wie „ requested array size exceeds VM limit “, „ PermGen space ”  oder “ <reason> <stack trace> (Native method). ” deuten auf andere Ursachen hin, z.B. Fragmentierung, Perm-Bereich zu klein, Probleme in JNI-Teilen der Anwendung, oder ähnliches.
 
 
 
 
 
 
 
Wie bekomme ich einen Heap Dump?
JVM-Schalter

 
 

Man kann die JVM mit diversen Optionen starten, die es später erlauben, Heap Dumps zu ziehen: 

-XX:+HeapDumpOnOutOfMemoryError 
Es wird automatisch ein Heap Dump erzeugt, wenn die JVM wegen einem OutOfMemoryError terminiert. 

-XX:+HeapDumpOnCtrlBreak
Erzeugt einen Heap Dump, wenn die entsprechende Tastenkombination an der Konsole eingegeben wird. 

-agentlib:hprof=heap=dump,format=b,doe=y
Verwendet den HPROF-Agenten, der auf die JVMTI-Schnittstelle aufsetzt, um einen „dump on exit“ (doe=y) zu erzeugen.  Das passiert dann immer beim Exit, auch wenn kein  OutOfMemoryError aufgetreten ist. 

Werkzeuge aus dem JDK

 
 

Man kann Werkzeuge verwenden, die zum JDK gehören: 

jmap –dump:format=[a|b],file=<fileName> pid

Dabei ist  pid die Java-Prozess-Id, die man mit Hilfe von  jps bestimmen kann. 

JConsole

Setzt auf die Java-Management-Beans (JMX beans) auf.  In der  HotSpotDiagnostic -Bean, einer Non-Standard-Bean, die es nur in der HotSpot-JVM von Sun/Oracle gibt, findet man die Operation  dumpHeap() , mit der ein Heap Dump erzeugt werden kann. 

 

Programmatisch

 
 

Man kann das Erzeugen eines Heap Dumps aus der Anwendung heraus anstoßen, beispielsweise über die oben erwähnte  HotSpotDiagnostic -Bean und deren  dumpHeap() -Methode.  Kommerzielle Profiler wie YourKit, JProfiler oder JProbe bieten zusätzlich die Möglichkeit an, Trigger zu setzen, mit denen Heap Dumps (aber auch andere Snapshots) erzeugt werden können.  Das geht sowohl aus dem Profiler heraus als auch über eine Programmierschnittstelle. 

 

Zusammenfassung

In diesem Artikel haben wir  die Suche nach Memory Leaks mit Hilfe eines Heap Dumps betrachtet. Eine solche Analyse des Heap Dumps wird oft post mortem gemacht, nachdem die Anwendung bereits wegen einem  OutOfMemoryError abgebrochen wurde.  Wir haben für die Heap-Dump-Analyse das frei verfügbare Werkzeug Memory Analyzer MAT verwendet, das auch sehr große Heap Dump problemlos verarbeiten kann. Alternativ kann man es auch mit einem kostenpflichtigen Profiler wie YourKit, JProfiler oder JProbe machen.  In allen Fällen wird man aber feststellen, dass die Werkzeuge nur Hilfestellungen bei der Memory Leak Analyse leisten können.  Die wirkliche Ursache muss der Entwickler selber finden; nur er versteht die Programmlogik.

Literaturverweise

/MEMLKS-1/ Memory Leaks - Ein Beispiel
Klaus Kreft & Angelika Langer, Java Magazin, August 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/64.Mem.Leaks/64.Mem.Leaks.html
/MAT/
Memory Analyzer (MAT)

Die gesamte Serie über Memory Leaks:

/MEMLKS-1/ Memory Leaks - Ein Beispiel
Klaus Kreft & Angelika Langer, Java Magazin, August 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/64.Mem.Leaks/64.Mem.Leaks.html
/MEMLKS-2/ Akkumulation von Memory Leaks
Klaus Kreft & Angelika Langer, Java Magazin, Oktober 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/65.Mem.Akkumulation/65.Mem.Akkumulation.html
/MEMLKS-3/ Memory Leaks - Referenzen "ausnullen"
Klaus Kreft & Angelika Langer, Java Magazin, Dezember 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/66.Mem.NullOut/66.Mem.NullOut.html
/MEMLKS-4/ Tools für die dynamisch Memory Leak Analyse
Klaus Kreft & Angelika Langer, Java Magazin, Februar 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/67.MemLeak.ToolCyclic/67.MemLeak.ToolCyclic.html
/MEMLKS-5/ Heap Dump Analyse
Klaus Kreft & Angelika Langer, Java Magazin, April 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/68.MemLeak.ToolDump/68.MemLeak.ToolDump.html
/MEMLKS-6/ Weak References
Klaus Kreft & Angelika Langer, Java Magazin, Juni 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/69.MemLeak.WeakRefs/69.MemLeak.WeakRefs.html

 
 
 
 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
 
Effective Java - Advanced Java Programming Idioms 
4 day seminar ( open enrollment and on-site)
High-Performance Java - Profiling and Tuning Java Applications
4 day seminar ( open enrollment and on-site)
 

 
  © Copyright 1995-2016 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/68.MemLeak.ToolDump/68.MemLeak.ToolDump.html  last update: 29 Nov 2016