Java Heapdumps erzeugen und verstehen (4. Akt)

Keine Kommentare

Im 2. Akt dieser Serie haben wir Java Memory Leaks vorgestellt. Dort wurde auch erläutert wie Memory Leaks in Java entstehen können und was Sie von klassischen Memory Leaks unterscheidet. Dieser Beitrag soll aufzeigen, wie man sich die Objekte im Heap im Detail anschaut. Inklusive der Referenzen zwischen den Objekten, um potentielle Java Memory Leaks zu identifzieren.

Das einfachste Werkzeug zur Analyse von Speicherproblemen sind so genannte Java Heapdumps, die man beispielsweise im Fehlerfall durch die JVM  Option -XX:+HeapDumpOnOutOfMemoryError automatisch erzeugen kann. In diesem Fall wird der Dump vor dem Beenden der JVM erzeugt – dabei spricht man dann auch von einer „Post mortem“ Analyse. Natürlich kann man diese Dumps auch manuell zur Laufzeit erzeugen, aber dazu später mehr.

Während wir uns in diesem Akt mit Heapdums beschäftigen, werden wir im nächsten Akt sehen welche anderen Alternativen es gibt um verlorenem Speicher auf die Spur zu kommen.

Java Heapdumps

Unter einem Heapdump versteht man eine textuelle oder binäre Abbildung des Java Heaps, die i.d.R. in eine Datei geschrieben wird. Aus den Informationen in einem Heapdump lassen sich mit verschiedenen Werkzeugen die gesamten Objekte, inklusive ihrer Referenzen untereinander, rekonstruieren. Es fehlt allerdings die Information in welchem Heap Bereich sich die Objekte befanden. Dies ist eigentlich schade, haben wir doch in Akt 3 gelernt welche Bedeutung die Bereiche haben können. Diese Information ist jedoch zum Finden von Java Memory Leaks in Heapdumps eher uninteressant.

Gesucht: Lebendig oder Tot

Heapdumps können zwei Arten von Objekten enthalten:

  • Nur lebendige Objekte (also Objekte welche über eine GC Root Referenz erreichbar sind)
  • Alle Objekte (also inklusive den nicht mehr referenzierten Objekten)

Da die lebendigen Objekte über verschiedene VM interne Mechanismen, wie Markierungslisten oder GC Roots, zu ermitteln sind, ist ein Heapdump mit ausschließlich lebendigen Objekten relativ schnell durch die JVM erstellbar.

Soll der Heapdump jedoch auch die nicht mehr referenzierten Objekte beinhalten, so dauert die Erzeugung viel länger und benötigt auch wesentlich mehr Speicherplatz.
In der Regel reichen die lebendigen Objekte auch schon für die Suche nach Memory Leaks aus. Nicht mehr referenzierte Objekte sind höchstens in der Betrachtung von Objekt Cycling oder GC Thrashing interessant. In diesen Fällen möchte man nicht nur nach Leaks suchen, sondern auch nach Objekten von denen unnötig viele erzeugt wurden.

Heapdumps erzeugen

Wie schon anfangs angedeutet, lassen sich Heapdumps auf zwei Arten erstellen. Doch der Erstellungsprozess eines Dumps führt zu einem Stillstand der Anwendungen, weil die Ausführung während der Erzeugung des Dumps gestoppt wird. Dies ist deshalb sinnvoll, weil sich ansonsten der Zustand des Heaps während der Erzeugung ändern würde. Deshalb ist von der übermäßigen Nutzung dieser Funktionalität in einem produktivem System abzuraten.

  1. Durch die JVM Option -XX:+HeapDumpOnOutOfMemoryError wird sobald die JVM sich mit einem OutOfMemoryError beendet ein Heapdump geschrieben.
    Es gibt keinen Grund diese Option nicht zu verwenden. Die JVM ist schon abgestürzt, und negative Performance uninteressant. Lediglich die Größe der Heapdumps stellt manche Server vor ein Problem.
    Da der Heapdump aber oft die einzige Informationsquelle über den Absturz ist, sollte er auf jeden Fall erzeugt werden. Nach einem Absturz sollte der Server Admin den Dump sichern, damit eine Post-mortem Analyse stattfinden kann, Leider werden sie häufig aus Platzgründen einfach gelöscht.
  2. Mit dem der JVM beliegendem Werkzeug jmap kann ein Heapdump von der Kommandozeile erzeugt werden:
    jmap -dump:live,format=b,file=<filename> <PID>

    Die Option live bewirkt, dass nur lebendige Objekte gedumped werden und kann auch weggelassen werden, will man den gesamten Heap sehen. Als Format bietet sich „b“ für „binär“ an. Es gibt auch eine ASCII Version, die auch manuell ausgewertet werden könnten – dies ist in der Praxis auf Grund der Datenmenge aber meist nicht möglich.

Heap, zeig was in Dir steckt!

Es gibt eine ganze Reihe von Werkzeugen mit denen Heapdumps offline untersucht werden können. Jedoch lohnt ein Vergleich hier nicht, da es einen konkurrenzlosen Gewinner gibt:
Eclipse Memory Analyzer Toolkit (Eclipse MAT)

Eclipse MAT kann Heapdumps verschiedenster Formate und Größen lesen, verarbeiten, analysieren und auswerten. Das Ganze mit einer hervorragend zu bedienenden, leider machnmal etwas zu mächtigen, Benutzeroberfläche und: völlig kostenlos. Ein wesentlicher Vorteil ist auch, dass MAT den Heapdump beim ersten Laden indiziert, so dass zum einen sehr große Heaps analysiert werden können und zum anderen die Analyse sehr schnell ist. Gerade ältere Tools hatten oft das Problem, dass der Heapdump zur Analyse komplett in den Arbeitsspeicher geladen wurde und man i.d.R. das 1,5-fache des Heaps zur Analyse benötigte. D.h. ein Heap von 2GB Größe benötigte 3 GB Heap zur Analyse.

Schaut man mit Eclipse MAT in einen Heap Dump hinein findet man viele Objekte. Üblicherweise solche:

  • char[]
  • java.lang.Object[]
  • java.lang.Class
  • java.lang.String
  • byte[]
  • java.util.HashMap
  • java.util.HashMap$Entry
  • short[]
  • int[]
  • java.util.ArrayList
  • java.lang.Integer

Diese Objekte werden immer in großer Anzahl zu finden sein. Jetzt ist nur die Frage: Sind diese Objekte das Problem oder wie finde ich den Verursacher?

 

 

 

Umfangreiche Leaks

Analysiert man einen OutOfMemoryError Heapdump post mortem, so kann in der Regel davon ausgegangen werden, dass das Memory Leak den Großteil des Speichers belegt hat. Doch wie groß ist das Leak? Im Heapdump sind alle Objektinstanzen vorhanden, Eclipse MAT zeigt auf dem Class Histogramm aber lediglich die Klassennamen, die Anzahl der Instanzen, sowie die Shallow Size. Bei der Shallow Size handelt es sich um die Größe aller primitiven Datentypen in der Instanz sowie die Referenzen auf andere Objekte. Interessant ist eher der Retained Heap, also die Gesamtgröße aller Objekte die von diesen Dominatoren lebendig gehalten werden. Um den Retained Heap anzuzeigen, muss MAT diesen erst errechnen, indem es alle Objektreferenzen verfolgt. Für die Suche nach MemoryLeaks ist in der Regel eine Sortierung nach Retained Heap hilfreich, weswegen MAT die Klassen mit den größten Retained Heaps in einem Tortendiagramm darstellt.

Ein prominentes Beispiel für viel Retained Heap aus Webanwendungen ist üblicherweise der

org.apache.catalina.session.StandardManager

Der fachliche Zweck ist klar: Benutzerdaten für die Zeit einer Sitzung festhalten. Doch häufig finden sich hier auch Frameworkinformationen, wie man an unserem Eintrag über Ajax4JSF sehen kann.

Aber auch eigene, und leider fast immer nicht korrekte, Cacheimplementierungen tauchen häufig im Tortendiagramm auf und liefern relativ schnell den Verursacher eines Memory Leaks.

Um zu bestimmen wer diese Objekte erzeugt, was diese Objeket fachlich machen und ob sie begründet noch lebendig sind, sind die konkreten Objektinstanzen und ihre eingehenden und ausgehenden Referenzen hilfreich.

Um an die Instanzen zu gelangen wählt man im Kontextmenü List Objects with incoming References. Dies zeigt alle Instanzen und eine Baumstruktur mit allen Referenzen auf dieses Objekt. Diese Referenzen sind also dafür verantwortlich, dass das Objekt nicht Garbage Collected wurde. Outgoing References sind etwas weniger interessant, kann aber helfen die Instanz zu identifizieren, indem man sehen kann was in diesem Objekt enthalten ist. Sie sind auch dann hilfreich, wenn der Dominator selbst, wie z.B. im Falle des SessionManagers, normal ist, jedoch unerwartet viele Objekte referenziert werden.

 

Schleichende Memory Leaks

Will man Leaks schon finden bevor ein OutOfMemoryError auftritt, so kann man mit der eingangs beschriebenen Methode Snapshots erstellen. Jedoch können Snapshots keine Aussage darüber treffen ob der aktuelle Zustand gut oder schlecht ist. Nur wenn Strukturen stetig wachsen, kann sich der Verdacht auf ein Leak erhärten.
Eclipse MAT bietet die Möglichkeit Heapdumps zu vergleichen und kann so wachsende Strukturen erkennen. Doch dafür bedarf es zweier Heapdumps zu gut abgestimmten Zeitpunkten. Mit ausreichend Last sollten schon einige Minuten zwischen den Dumps liegen, bei wenig Last eher Stunden. Nur so lassen sich wirklich deutliche Unterschiede fernab von „natürlichen Schwankungen“ feststellen.

Im folgenden Beispiel erkennt man zum Beispiel eine deutliche Erhöhung von XML bezogenen Objekten:

Bessere Methoden

Doch Snapshots zu vergleichen ist umständlich und im Extremfall sogar irreführend, und OutOfMemoryError in Produktion sind sehr ärgerlich. Nicht zuletzt fehlt uns nach der Analyse immer noch die Information an welchen Codestellen die Objekte erzeugt und dann wahrscheinlich nicht mehr dereferenziert werden.
Gibt es also keine besseren Methoden?
Aber sicher! Profiler und einige APM Werkzeuge können während der Laufzeit des Programms Statistiken mitführen und Zugriffspfade aufzeichnen. Die von diesen Programmen gelieferten Informationen sind oftmals schneller zielführend als ein Heapdump und werden der Hauptakteur unseres nächsten Aktes sein.

Fabian Lange ist Lead Agent Engineer bei Instana und bei der codecentric als Performance Geek bekannt. Er baut leidenschaftlich gerne schnelle Software und hilft anderen dabei, das Gleiche zu tun.
Er kennt die Java Virtual Machine bis in die letzte Ecke und beherrscht verschiedenste Tools, die JVM, den JIT oder den GC zu verstehen.
Er ist beliebter Vortragender auf zahlreichen Konferenzen und wurde unter anderem mit dem JavaOne Rockstar Award ausgezeichnet.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.