Der Eclipse Memory Analyzer

Analyze me!

SAP hat zu Eclipse ein mächtiges Tool zur Analyse von Out-Of-Memory-Situationen in Java-Anwendungen beigesteuert, den „Eclipse Memory Analyzer“. Mit Eclipse 3.5 (Galileo) ist dieser erstmals Teil der koordinierten Veröffentlichung verschiedener Eclipse-Projekte. Neben der reinen Analyse von Speicherlecks kann das Tool noch einiges mehr und hilft bei einer Vielzahl von Problemen mit Java-Anwendungen. Dieser Artikel beschreibt die Verwendung des Memory Analyzers und stellt die verschiedenen Einsatzmöglichkeiten im Bereich Speicher-Analyse und Performance-Verbesserung vor. Anhand einiger Beispiel-Anwendungen wird gezeigt, wie der Eclipse Memory Analyzer wertvolle Dienste für jede Art von Java-Entwicklung leisten kann.

Kennen Sie das Gefühl, wenn ein Kunde in der ausgelieferten Java-Anwendung einen schwerwiegenden Fehler meldet und man sich beinahe machtlos fühlt, weil das Problem einerseits extrem schwer nachzuvollziehen ist und es andererseits auch den Produktionsbetrieb beim Kunden gefährdet? Häufig sind Speicher-Probleme (mit)verantwortlich, oft ist das erste Indiz der berüchtigteOutOfMemoryError. Für diesen Teilbereich hat Eclipse seit Version 3.5 ein probates Werkzeug an Bord, welches in vielen Fällen helfen kann.

Warum sind Tools zur Speicheranalyse nötig?

Es ist üblicherweise sehr aufwendig bis nahezu unmöglich, Probleme mit dem Speicherverbrauch in Java-Anwendungen manuell zu analysieren. Die automatische Speicherverwaltung in Java mit Garbage Collection macht es noch schwieriger, weil dadurch der angezeigte Speicherverbrauch nicht mit der aktuellen Situation übereinstimmt, da der Speicher eigentlich schon frei ist, Programme wie „top“ und „ps“ aber dies nicht darstellen.

Geschichte des Eclipse Memory Analyzer

SAP hat mit dem „Eclipse Memory Analyzer“ ein mächtiges Tool zur Analyse von Out-Of-Memory-Situationen beigesteuert [5]. Der Eclipse Memory Analyzer (abgekürzt „MAT“ für „Memory Analyzer Tool“) erlaubt es, sogenannte Heapdumps zu analysieren. Diese Heapdumps sind Speicherabbilder von Java-Anwendungen, die alle Objekte in der Java VM zu diesem Zeitpunkt enthalten. Es ist also eine Art Momentaufnahme des kompletten Zustands der VM. Diese Heapdumps können zur Laufzeit gezogen und anschließend offline analysiert werden. Die Funktionalität, Heapdumps zu erzeugen, wird mit der jeweiligen Java VM mitgeliefert. Bei Sun JVM kann das ab Java 6 das Tool „jmap“. Andere Java VMs (JRockit, IBM, …) haben teilweise andere Binärformate für den Heapdump, MAT bringt aber Unterstützung für eine große Anzahl dieser Formate mit, siehe [3].

Download/Installation

Der Memory Analyzer wird als Eclipse Plug-in zur Verfügung gestellt. Da das Tool seit Eclipse 3.5 Teil der koordinierten Release ist, lässt es sich dort einfach über die Eclipse-Update-Funktionalität installieren. Wer neuere – und evtl. instabile – Versionen ausprobieren möchte, kann die Update-Site http://download.eclipse.org/technology/mat/latest/update-site/ verwenden.

Zusätzlich bietet das Projektteam auch eine Standalone-Version des MAT, bei der alle nötigen Teile von Eclipse enthalten sind und das Ganze somit als eigenständig lauffähige Anwendung zur Verfügung steht.

In der Praxis hat sich gezeigt, dass der MAT bei großen Heapdumps sehr speicherhungrig werden kann, deshalb empfiehlt sich der Einsatz der Standalone-Variante, da dadurch die normale Eclipse IDE nicht in Mitleidenschaft gezogen wird.

Nach dem Download von [1] und dem Entpacken der Zip-Datei in ein neues Verzeichnis kann die ausführbare Datei MemoryAnalyzer gestartet werden.

Falls das Plug-in in der normalen Eclipse IDE verwendet wird, kann nach erfolgreicher Installation via Open Perspective auf Memory Analyzer die gleiche Umgebung wie in der Standalone-Variante erreicht werden.

[ header = Seite 2: Vom Speicherproblem zum Heapdump ]

Vom Speicherproblem zum Heapdump

Wie erhält man nun einen Heapdump aus einer Anwendung? Dazu gibt es mehrere Möglichkeiten:

  • Per Commandline, während die Anwendung läuft.
  • Per Option beim Start der Java VM, sobald ein „OutOfMemoryError“ auftritt (siehe [4]).

Am einfachsten ist es, wenn man Sun Java 6 verwendet, denn dann liefert das folgende Kommando einen Heapdump an der gewählten Stelle. Dazu muss man über „top“, „ps“ oder den Windows Task Manager die sogenannte ProcessId oder PID herausfinden und in den Befehl einsetzen:

jmap -dump:file=/tmp/jmap.hprof <processid>

Unter Windows:

jmap -dump:file=C:TEMPjmap.hprof <processid>

Wichtig ist hier die Endung .HPROF, da MAT nur Dateien mit dieser Endung anzeigt. Es können laut [3] auch eine Reihe anderer JDKs/JVMs verwendet werden, die jeweiligen Befehle und unterstützen Arten, einen Heapdump zu erhalten, listet [4]. Auch IBM JDK und Oracle (BEA) JRockit können entsprechende Heapdumps erzeugen.

Starten des Memory Analyzer

Nachdem der Heapdump generiert ist, kann man sich an die Analyse machen. Zu Beginn begrüßt ein Startbildschirm den Anwender, auf dem auch die Dokumentation und weitere Informationen verfügbar sind.

Abb. 1: Der Startbildschirm

Öffnen eines Heapdumps

Im Menüpunkt „File“ kann mit „Open Heapdump“ die .HPROF Datei ausgewählt werden. Anschließend öffnet sich ein Wizard, der es erlaubt, verschiedene Auswertungen durchzuführen.

Abb. 2: Der Auswertungs-Wizard

Mit „Cancel“ kann man den Dialog abbrechen, die Auswertungen können jederzeit später durchgeführt werden.

Jetzt zeigt MAT einen Überblick über den Heapdump an. Darin ist ersichtlich, wie groß der Heap in der Java-Anwendung war und welche Objekte am meisten Speicher „halten“. „Retained Size“ meint hier, wie viel Speicher durch das Objekt und alle Attribute und Kind-Objekte im Speicher gehalten wird.

Abb. 3: Anzeige der generellen Information über den Heapdump

[ header = Seite 3: Wizard: Leak report ]

Wizard: Leak report

Wird im Wizard „Leak report“ ausgewählt, so analysiert MAT die .HPROF-Datei und liefert eine Liste von „Leak suspects“ (siehe Abb. 4) . Das sind Objekte, bei denen MAT feststellt, dass sie eventuell ein Leak darstellen. Es sind also nicht unbedingt Probleme, sondern nur die „besten Kandidaten“ für eine genauere Analyse. In vielen Fällen werden hier diejenigen Objekte gefunden, die durch das Design der Anwendung den Hauptteil des Speichers halten. Manchmal gibt aber schon diese Liste Hinweise für erste Ergebnisse.

Abb. 4: Die Anzeige der Objekte mit großem oder suspektem Speicherverbrauch

Hier ein einfaches Beispiel: Die folgende Anwendung speichert endlos Strings in eine Map, erhöht also den Speicherverbrauch der Anwendung konstant:

public class SimpleMemoryLeak {
static Map<Long, String> cache = new HashMap<Long, String>();

static void addToCache(Long t, String str) {
if(!cache.containsKey(t)) {
cache.put(t, str);
}
}

public static void main(String[] args) throws Exception {
// Eine einfache Endlos-Schleife die Elemente in einen
// Cache mit fehlerhaftem "Key" einfügt
while (true) {
for (int i = 0; i < 500; i++) {
addToCache(System.currentTimeMillis(),
new String("teststring"));
}
Thread.sleep(100);
}
}
}

Wenn nach einiger Zeit nach dem Start dieses Programms ein Heapdump erzeugt wird, zeigt der Bericht die oben angezeigten „Leak Suspects“. In diesem Fall wird der Classloader als das „Toplevel“-Objekt ermittelt, welches also selber noch nicht wirklich ein Problem ist, sondern nur in weiterer Folge die Map hält, da diese ein statisches Objekt ist und daher keiner Objektinstanz angehört.

Weitere Details erhält man durch Auswählen von „Details>> „, wodurch man eine genauere Aufstellung über den möglichen Grund des Übels erhält (siehe Abb. 5).

Abb. 5: Details zu den vermuteten Speicherproblemen

Wizard: Component report

Die zweite Möglichkeit im Wizard ist „Component report“. Bei dieser Option werden eine ganze Reihe vorgefertigter Auswertung auf den Heapdump ausgeführt. So kann hier nach doppelten Strings oder nach Collections mit ungünstiger Speicherausnutzung gesucht werden. Zu diesen Themen später noch mehr.

Nach Drücken von „Next“ verlangt MAT noch die Eingabe einer Regular Expression, um den Report nur auf bestimmte Java-Packages anzuwenden. Mit „.*“ werden alle Objekte im Report inkludiert (siehe Abb. 6).

Abb. 6: Auswahl der zu untersuchenden Java-Packages via Regular Expression

Alle Auswertungen im „Component Report“ können übrigens später einzeln über die Toolbar ausgeführt werden, der Wizard ist nicht unbedingt dafür nötig.

[ header = Seite 4: Doppelte Strings und was sie bedeuten ]

Doppelte Strings und was sie bedeuten

Strings in Java sind „immutable“, also unveränderbar und werden normalerweise „per reference“ übergeben. Es gibt also einen String nur ein einziges Mal und alle Objekte, die den String halten, haben eine Referenz auf ein einziges Objekt. In einigen Situationen wird allerdings der selbe Text in verschiedenen physikalischen String-Objekten im Speicher erzeugt, wodurch unnötig Speicher verbraucht wird, da die Objekte ja alle auf ein String-Objekt zeigen könnten. Dies ist dann der Fall, wenn Strings aus anderen Formaten erzeugt werden, also z.B. bei Remoting, beim XML-Parsen und bei der Erzeugung von Strings aus Byte-Arrays. Folgendes einfaches Beispiel illustriert dieses Verhalten:

public class DuplicateStrings {
static Map<Long, String> cache = new HashMap<Long, String>();

static void addToCache(Long t, String str) {
if(!cache.containsKey(t)) {
cache.put(t, str);
}
}

public static void main(String[] args) throws Exception {
// Eine einfache Endlos-Schleife die endlos doppelte Strings
// aus einem Byte-Array in einen Cache einfügt
byte[] bytes = "teststring".getBytes();
while (true) {
for (int i = 0; i < 500; i++) {
addToCache(System.currentTimeMillis(),
new String(bytes));
}
Thread.sleep(100);
}
}
}

Der String wird hier jedes Mal aus einem Byte-Array neu erstellt. Es wird deshalb jeweils ein neuer String mit dem exakt gleichen Inhalt angelegt und nicht ein bereits existierender konstanter String verwendet.

Abb. 7: Anzeige der gefundenen doppelten Strings in der Übersicht

MAT zeigt solche Fälle im „Component Report“, wie in Abbildung 7 ersichtlich, an. Es ist hier schon zu sehen, dass sich der String „teststring“ 1153 Mal wiederholt und dadurch eine Menge Speicher verbraucht wird. Durch Auswählen von „Details>> “ erfährt man auch, wie viel Speicher ungefähr verbraucht wird, in diesem Beispiel „mehr als 192456 Bytes“ (Abbildung 8).

Abb. 8: Anzeige der gefundenen doppelten Strings in der Detailansicht

Solche Probleme lassen sich oft durch die Verwendung von String-Internalizing lösen, Details unter [15] und [16].

Falsche Verwendung von Collections und Arrays

MAT liefert auch Details zu der Benutzung von Collections und Arrays. Es gibt hier verschiedene Situationen, die entweder unnötig Speicher verschwenden oder im Hinblick auf die Ausführungsgeschwindigkeit negative Auswirkungen haben. Ein klassisches Beispiel ist hier die HashMap, die sehr schlecht funktioniert, wenn der Hash-Schlüssel nicht gleichmäßig verteilt wird. Im schlimmsten Fall wird das Suchen in einer HashMap so langsam wie das Suchen in einer Liste. MAT liefert hier Auswertungen für folgende Dinge:

  • Collection Fill Ratio: Wenn Collections mit großen Initialgrößen erstellt werden, diese später diese Bereich aber nicht ausnützen, wird unnötig Speicher verschwendet. Diese Auswertung zeigt solche Fälle an, indem die Collections in Gruppen je nach dem „Füllgrad“ eingeteilt werden, siehe Abbildung 9.
  • Map Collision Ratio: Hier wird errechnet, welche der Map-Objekte schlechte Hash-Funktionen benützen und dadurch zu einer ungleichmäßigen Verteilung führen. Dies kann vor allem die Performance beim Suchen sehr negativ beeinflussen.

Abb. 9: Anzeige zu Collections, die nicht optimal gefüllt sind

Unter [10] findet sich ein ausführlicher Blog-Eintrag eines der Hauptentwickler von MAT zum Thema „Collection-Analyse“ in MAT mit detaillierter Beschreibung dieser Funktionen.

[ header = Seite 5: Beliebige Abfragen auf den Heapdump ]

Beliebige Abfragen auf den Heapdump

Als sehr mächtiges Feature bietet MAT die Möglichkeit, SQL-ähnliche Abfragen auf den Inhalt des Heapdumps auszuführen. Dies ist sehr ähnlich zu jhat, welches Teil von Sun JDK ist (siehe [14]). Die Abfragesprache wird OQL (für Object-Query-Language) genannt, das Abfragefenster ist über einen eigenen Toolbar-Button erreichbar. Dort können Abfragen durch – ausgeführt werden. Die Beispiel-Abfrage in Abbildung 10 sucht alle Strings mit mehr als 400 Zeichen.

Abb. 10: Eine einfache OQL-Query

MAT bietet hier noch einige weitere Möglichkeiten, etwa alle Objekte, die von einem Interface ableiten (SELECT * FROM INSTANCEOF ….), verschiedene Formatierungen der Ergebnisse (toString(), DISTINCT, …) oder spezielle WHERE-Bedingungen (inbounds(), outbounds(), classof(), …). Dieser Artikel kann hier nicht weiter ins Detail gehen, sowohl der Artikel in [12] als auch die Online-Hilfe zu diesem Thema beschreiben die Möglichkeiten der Abfragesyntax im Detail.

MAT kann auch einige speziellen Dinge prüfen, die bei der Entwicklung von Eclipse-Plug-ins von Bedeutung sind. Dadurch können sehr schwer zu findende Problem aufgedeckt werden, siehe [12] für weitere Informationen dazu.

Fazit

Der Eclipse Memory Analyzer ist ein mächtiges Tool, welches von SAP und freiwilligen Comittern weiterentwickelt wird. Es bietet Möglichkeiten, die kein anderes der gängigen Programme zur Speicheranalyse bietet. Vor allem das Auffinden von doppelten Strings und die detaillierte Analyse von Collections/Arrays ermöglichen es, Programme präventiv auf Speicherprobleme hin zu überprüfen und so Probleme im Echtbetrieb erst gar nicht mehr auftreten zu lassen.

Einzig die Bedienung ist durch die vielen Funktionen zu Beginn etwas unübersichtlich. Sobald man allerdings die Grundlagen verstanden hat, lässt sich sehr schnell zu jedem Heapdump eine Vielzahl von Erkenntnissen gewinnen.

Neben dem Eclipse Memory Analyzer gibt es natürlich eine Reihe weiterer Tools, die bekanntesten sind hier:

  • „Visual VM“ [13], welches sowohl offline Heapdumps analysieren als auch Anwendungen direkt überwachen und darstellen kann.
  • „jhat“ [14] wird mit dem JDK mitgeliefert und erlaubt es, einen Heapdump über eine Web-Schnittstelle mit SQL-ähnlichen Abfragen zu analysieren. Das ist allerdings eher etwas für Profis, denn die Abfragen werden schnell komplex und unübersichtlich.

Dieser Artikel konnte einige Funktionalitäten nicht genauer erklären, die Webseiten unter [1] geben grundsätzliche Bedienungshinweise, weitere Informationen gibt es im Eclipse-Blog [7]. Außerdem gibt es ein aktives Forum [8] und eine Entwickler-Mailingliste [9], wo alle weiteren Fragen beantwortet werden.

Geschrieben von
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.