Wie lese ich einen Thread-Dump in Java?
Diagnosehandbuch
In diesem Kapitel wird beschrieben, wie Sie Oracle JRockit JVM-Thread-Dumps abrufen und verwenden. Grundlegende Hintergrundinformationen zu Threads und Threadsynchronisierung finden Sie unter Grundlegendes zu Threads und Sperren.
Ein Threaddump ist eine Momentaufnahme des Zustands aller Threads, die Teil des Prozesses sind. Der Status jedes Threads wird mit einem sogenannten Stack-Trace dargestellt, der den Inhalt des Stacks eines Threads anzeigt. Einige der Threads gehören zu der Java-Anwendung, die Sie ausführen, während andere interne JVM-Threads sind.
Ein Thread-Dump enthält Informationen über die Thread-Aktivität einer Anwendung, die Ihnen helfen können, Probleme zu diagnostizieren und die Leistung von Anwendungen und JVMs besser zu optimieren. Thread-Dumps zeigen z. B. automatisch das Auftreten eines Deadlocks an. Deadlocks führen dazu, dass eine Anwendung ganz oder teilweise angehalten wird.
In diesem Kapitel werden folgende Themen behandelt:
Erstellen von Thread-Dumps
Um einen Thread-Dump aus einem Prozess zu erstellen, führen Sie einen der folgenden Schritte aus:
- Drücken Sie die Taste, während der Prozess ausgeführt wird (oder indem Sie ihn unter Linux an den Prozess senden).
- Geben Sie beim Start in der Befehlszeile Folgendes ein:
Der Thread-Dump wird in der Befehlszeile angezeigt.
Lesen von Thread-Dumps
In diesem Abschnitt wird der typische Inhalt eines Thread-Dumps beschrieben, indem ein Beispiel-Thread-Dump von Anfang bis Ende durchlaufen wird. Zunächst wird ein Beispiel-Thread-Dump vorgestellt, der in seine Bestandteile zerlegt ist (siehe Listing 20-1, Listing 20-2, Listing 20-3, Listing 20-4 und Listing 20-5). Zuerst werden Informationen über den Hauptthread gedruckt, dann alle internen JVM-Threads, gefolgt von allen anderen Java-Anwendungsthreads (falls vorhanden). Abschließend werden Informationen zu Schlossketten gedruckt.
Der Beispielthread dump wird von einem Programm übernommen, das drei Threads erstellt, die schnell in einen Deadlock gezwungen werden. Die Anwendungsthreads Thread-0, Thread-1 und Thread-2 entsprechen drei verschiedenen Klassen im Java-Code.
Der Anfang des Thread-Dumps
Der Thread-Dump beginnt mit dem Datum und der Uhrzeit des Dumps und der Versionsnummer der verwendeten JRockit-JVM (siehe Listing 20-1).
Listing 20-1 Die ersten Informationen eines Thread-Dumps
=============== Wed Feb 21 13:46:45 2007
BEA JRockit(R) R27.1.0-109-73164-1.5.0_08-20061129-1428-windows-ia32
Stack Trace für den Hauptanwendungs-Thread
Listing 20-2 zeigt den Stack-Trace des Hauptanwendungsthreads. Es gibt eine Thread-Informationszeile, gefolgt von Informationen über Sperren und einer Ablaufverfolgung des Stapels des Threads zum Zeitpunkt des Thread-Dumps.
Listing 20-2 Der Hauptthread in der thread dump
-- Warten auf Benachrichtigung auf: util/repro/Thread1@0x01226528[fat lock]
at jrockit/vm/Threads.waitForSignal(J)Z(Native Methode)
at java/lang/Object.wait(J)V(Native Methode)
at java/lang/Thread.join(Thread.java:1095)
^-- Sperre während des Wartens freigegeben: util/repro/Thread1@0x01226528[fat lock]
at java/lang/Thread.join(Thread.java:1148)
at util/repro/DeadLockExample.main(DeadLockExample.java:23)
at jrockit/vm/RNI.c2java(IIII)V(Native Methode)
-- Ende der Ablaufverfolgung
Nach dem Namen und anderen Identifikationsinformationen werden die verschiedenen Statusmeldungen des Hauptthreads ausgegeben. Der Hauptthread in Listing 20-2 ist ein laufender Thread (), er führt entweder internen JVM-Code oder benutzerdefinierten JNI-Code () aus und wartet derzeit auf die Freigabe eines Objekts (). Wenn ein Thread auf eine Benachrichtigung wartet Bei einer Sperre (durch Aufruf von ) wird dies am oberen Rand des Stack-Trace als angezeigt.
Sperren und Sperrketten
Für jeden Thread gibt die JRockit JVM die folgenden Informationen aus:
- Wenn der Thread versucht, eine Sperre zu nehmen (um einen synchronisierten Block einzugeben), die Sperre jedoch bereits von einem anderen Thread gehalten wird, wird dies oben im Stack-Trace als "Blockiert beim Versuch, eine Sperre zu erhalten" angezeigt.
- Wenn der Thread auf eine Benachrichtigung für eine Sperre wartet (durch Aufrufen von ), wird dies oben in der Stapelüberwachung als "Warten auf Benachrichtigung" angezeigt.
- Wenn der Thread Sperren angenommen hat, wird dies im Stack-Trace angezeigt. Nach einer Zeile im Stack-Trace, die einen Funktionsaufruf beschreibt, folgt eine Liste der Sperren, die vom Thread in dieser Funktion übernommen wurden. Dies wird wie folgt beschrieben (wobei das als Erinnerung daran dient, dass die Sperre in der Funktion oberhalb der Zeile mit der Sperre geschrieben ist).
das Die Semantik für das Warten (auf eine Benachrichtigung) auf ein Objekt in Java ist etwas komplex. Um einen synchronisierten Block einzugeben, müssen Sie zuerst die Sperre für das Objekt festlegen und dann dieses Objekt aufrufen. In der wait-Methode wird die Sperre aufgehoben, bevor der Thread tatsächlich in den Ruhezustand wechselt und auf eine Benachrichtigung wartet. Wenn es eine Benachrichtigung erhält, nimmt wait die Sperre erneut, bevor es zurückkehrt. Wenn also ein Thread eine Sperre genommen hat und auf diese Sperre wartet (auf eine Benachrichtigung), wird die Zeile im Stack-Trace, die beschreibt, wann die Sperre genommen wurde, nicht als "Sperre halten" angezeigt, sondern als "Sperre während des Wartens freigegeben".
Alle Sperren werden wie folgt beschrieben: zum Beispiel:
Beschreiben Sie das Objekt, zu dem das Schloss gehört. Der Klassenname ist eine exakte Beschreibung, der vollqualifizierte Klassenname des Objekts. ist hingegen eine temporäre ID, die nur für einen einzelnen Thread-Dump gültig ist. Das heißt, Sie können darauf vertrauen, dass, wenn ein Thread A eine Sperre hält und ein Thread B in einem einzelnen Threaddump auf eine Sperre wartet, es sich um dieselbe Sperre handelt. Wenn Sie nachfolgende Threaddumps ausführen, ist dies jedoch nicht vergleichbar, und selbst wenn ein Thread die gleiche Sperre besitzt, kann er eine andere haben, und umgekehrt garantiert diese nicht, dass er dieselbe Sperre hält. beschreibt den internen JVM-Typ der Sperre (fat, thin, rekursiv oder lazy). Der Status von aktiven Sperren (Monitoren) wird auch in Stack-Traces angezeigt.
Darstellung von Sperren in falscher Reihenfolge
Die Zeilen mit den Sperreninformationen sind aufgrund von Compiler-Optimierungen möglicherweise nicht immer korrekt. Dies bedeutet zwei Dinge:
- Wenn ein Thread in derselben Funktion zuerst Sperre A und dann Sperre B verwendet, ist die Reihenfolge, in der sie gedruckt werden, nicht angegeben.
- Wenn ein Thread in der Methode method aufruft und eine Sperre A in annimmt, wird die Sperre möglicherweise als übernommen in gedruckt.
Normalerweise sollte dies kein Problem sein. Die Reihenfolge der Sperrlinien sollte sich nie stark von ihrer korrekten Position entfernen. Außerdem fehlen nie Sperrzeilen – Sie können sicher sein, dass alle von einem Thread übernommenen Sperren im Thread-Dump angezeigt werden.
JVM Internal
Threads Listing 20-3 zeigt die Ablaufverfolgungen von JVM-Innengewinden. Die Threads wurden als Daemon-Threads markiert, was an ihren Statusindikatoren zu erkennen ist. Daemon-Threads sind entweder interne JVM-Threads (wie in diesem Fall) oder Threads, die mit als Daemon-Threads gekennzeichnet sind.
Listing 20-3 Der erste und letzte Thread in einer Liste von JVM-internen Threads
Wie Sie sehen können, werden Sperrinformationen und Stack-Traces für die internen JVM-Threads in Liste 20-3. Dies ist die Standardeinstellung.
Wenn Sie Stack-Traces für die internen JVM-Threads anzeigen möchten, verwenden Sie den Parameter, wenn Sie den Handler senden. Schreiben Sie auf der Befehlszeile Folgendes:
Andere Java-Anwendungsthreads
Normalerweise interessieren Sie sich hauptsächlich für die Threads der Java-Anwendung, die Sie ausführen (einschließlich des Hauptthreads). Alle Java-Anwendungsthreads mit Ausnahme des Hauptthreads werden am Ende des Thread-Dumps angezeigt. Listing 20-4 zeigt die Stack-Traces von drei verschiedenen Anwendungsthreads.
Auflistung 20-4 Zusätzliche Anwendungsthreads
-- Blockiert beim Versuch, eine Sperre zu erhalten: java/lang/Object@0x01226300[fat lock]
at jrockit/vm/Threads.waitForSignal(J)Z(Native Methode)
at jrockit/vm/Locks.fatLockBlockOrSpin(ILjrockit/vm/ObjectMonitor;II)V(Unbekannt Source)
unter jrockit/vm/Locks.lockFat(Ljava/lang/Object; ILjrockit/vm/ObjectMonitor; Z)Ljava/lang/Objekt; (Unbekannte Quelle)
at
jrockit/vm/Locks.monitorEnterSecondStage(Ljava/lang/Object; i)ljava/lang/Objekt; (Unbekannte Quelle)
bei jrockit/vm/Locks.monitorEnter(Ljava/lang/Object;)ljava/lang/Objekt; (Unbekannte Quelle)
at util/repro/Thread1.run(DeadLockExample.java:34)
^-- Haltesperre: java/lang/Object@0x012262F0[dünne Sperre]
^-- Haltesperre: java/lang/Object@0x012262F8[dünne Sperre]
bei jrockit/vm/RNI.c2java(IIII)V(Native Methode)
-- Ende der Spur
-- Blockiert beim Versuch, eine Sperre zu erhalten: java/lang/Object@0x012262F8[dünne Sperre]
bei jrockit/vm/Threads.sleep(I)V(Native Methode)
bei jrockit/vm/Locks.waitForThinRelease(Ljava/lang/Object; I)I (Unbekannte Quelle)
bei jrockit/vm/Locks.monitorEnterSecondStage(Ljava/lang/Objekt; i)ljava/lang/Objekt; (Unbekannte Quelle)
bei jrockit/vm/Locks.monitorEnter(Ljava/lang/Object;)ljava/lang/Objekt; (Unbekannte Quelle)
at util/repro/Thread2.run(DeadLockExample.java:48)
at jrockit/vm/RNI.c2java(IIII)V(Native Methode)
-- Ende der Spur
"Thread-2" id=13 idx=0x22 tid=48416 prio=5 alive, in native, blockiert
-- Blockiert beim Versuch, eine Sperre zu erhalten: java/lang/Object@0x012262F8[dünne Sperre]
bei jrockit/vm/Threads.sleep(I)V(Native Methode)
bei jrockit/vm/Locks.waitForThinRelease(Ljava/lang/Object; I)I(Unbekannte Quelle)
bei jrockit/vm/Locks.monitorEnterSecondStage(Ljava/lang/Object; i)ljava/lang/Objekt; (Unbekannte Quelle)
bei jrockit/vm/Locks.monitorEnter(Ljava/lang/Object;)ljava/lang/Objekt; (Unbekannte Quelle)
bei util/repro/Thread3.run(DeadLockExample.java:65)
^-- Haltesperre: java/lang/Object@0x01226300[fette Sperre]
bei jrockit/vm/RNI.c2java(IIII)V(Native Methode)
-- Ende der Ablaufverfolgung
Alle drei Threads befinden sich in einem blockierten Zustand (gekennzeichnet durch ), was bedeutet, dass sie alle versuchen, synchronisierte Blöcke einzugeben. Thread-0 versucht, Object@0x01226300[Fat Lock] zu nehmen, aber dies wird von Thread-2 gehalten. Sowohl Thread-2 als auch Thread-1 versuchen, Object@0x012262F8[dünne Sperre] zu übernehmen, aber diese Sperre wird von Thread-0 gehalten. Dies bedeutet, dass Thread-0 und Thread-2 einen Deadlock bilden, während Thread-1 blockiert ist.
Lock Chains
Ein herausragendes Merkmal der JRockit JVM ist, dass sie automatisch Deadlocks, blockierte und offene Lock-Chains unter den laufenden Threads erkennt. Die Analyse in Listing 20-5 zeigt alle Lock-Ketten, die von den Threads T1, T2, T3, T4 und T5 erstellt wurden. Diese Informationen können verwendet werden, um Ihren Java-Code zu optimieren und Fehler zu beheben.
Listing 20-5 Deadlock-Ketten und blockierte Lock-Chains
================================= Kette 6:
"Tot T1" id=16 idx=0x48 tid=3648 wartet auf java/lang/Object@0x01225018 gehalten von:
"Tot T3" id=18 idx=0x50 tid=900 wartet auf java/lang/Object@0x01225010 gehalten von:
"Tot T2" id=17 idx=0x4c tid=3272 wartet auf java/lang/Object@0x01225008 gehalten von:
"Tot T1" id=16 idx=0x48 tid=3648
===================
Kette 7:
"Blockierter T2" id=20 idx=0x58 tid=3612 Warten auf java/lang/Object@0x01225310 gehalten von:
"Blockierter T1" id=19 idx=0x54 tid=2500 Warten auf java/lang/Object@0x01224B60 gehalten von:
"Offener T3" id=13 idx=0x3c tid=1124 in Kette 1
================
Kette 1:
"Offen T5" id=15 idx=0x44 tid=4048 Warten auf java/lang/Object@0x01224B68 gehalten von:
"T4 öffnen" id=14 idx=0x40 tid=3380 warten auf java/lang/Object@0x01224B60 gespeichert von:
"Open T3" id=13 idx=0x3c tid=1124 waiting for java/lang/Object@0x01224B58 kept by:
"Open T2" id=12 idx=0x38 tid=3564 waiting for java/lang/Object@0x01224B50 kept by
: "Open T1" id=11 idx=0x34 tid=2876 (active)
Thread-Status in Thread-Dumps
Dieser Abschnitt beschreibt die verschiedenen Status oder Zustände, die ein Thread in einem Thread-Dump anzeigen kann. Es gibt drei Arten von Zuständen:
Lebensdauerzustände
Tabelle 20-1 beschreibt die Lebensdauerzustände, die ein Thread in einem Threadabbild anzeigen kann.
Zustandsbeschreibung |
|
---|---|
| Dies ist ein normaler, ausgeführter Thread. Praktisch alle Threads im Threaddump sind aktiv. |
| Es wurde angefordert, dass der Thread von gestartet wird, aber der eigentliche Betriebssystemprozess hat dies noch nicht getan gestartet oder weit genug ausgeführt, um die Steuerung an die JRockit JVM zu übergeben. Es ist äußerst unwahrscheinlich, dass dieser Wert zu sehen ist. Ein Objekt, das erstellt, aber noch nicht ausgeführt wurde, wird nicht im Threaddump angezeigt. |
| Dieser Thread hat seine Methode beendet und auch alle Threads benachrichtigt, die ihm beitreten, aber er wird immer noch in der internen Threadstruktur der JVM für laufende Threads gespeichert. Es ist äußerst unwahrscheinlich, dass dieser Wert zu sehen ist. Ein Thread, der für einen Zeitraum von mehr als ein paar Millisekunden beendet wurde, wird nicht im Threadabbild angezeigt. |
Ausführungszustände
Tabelle 20-2 beschreibt die Ausführungszustände, die ein Thread in einem Threadabbild anzeigen kann.
| Zustand Beschreibung |
---|---|
| Dieser Thread hat versucht, einen synchronisierten Block einzugeben, aber die Sperre wurde von einem anderen Thread übernommen. Dieser Thread ist blockiert bis die Verriegelung gelöst wird. |
| Dies ist derselbe Zustand wie , jedoch mit der zusätzlichen Information, dass es sich bei der fraglichen Sperre um eine dünne Sperre handelt. |
| Dieser Thread hat ein Objekt aufgerufen. Der Thread verbleibt dort, bis ein anderer Thread eine Benachrichtigung für dieses Objekt sendet. |
| Dieser Thread hat . |
Dieser Thread hat aufgerufen |
| .
| Die Ausführung des Threads wurde angehalten von oder ein JVMTI/JVMPI-Agent ruft |
Special States
auf. Tabelle 20-3 beschreibt die speziellen Zustände, die ein Thread in einem Thread-Dump anzeigen kann. Beachten Sie, dass sich alle diese Zustände nicht gegenseitig ausschließen.
Fehlerbehebung bei Thread-Dumps
Dieser Abschnitt enthält Informationen zur Verwendung von Threaddumps für die Problembehandlung und Diagnose.
Um Threaddumps für die Problembehandlung zu verwenden, müssen Sie nicht nur Deadlocks erkennen, sondern auch mehrere Threaddumps aus demselben Prozess verwenden. Wenn Sie jedoch eine langfristige Analyse des Verhaltens durchführen möchten, ist es wahrscheinlich hilfreicher, gelegentliche Thread-Dumps mit anderen Diagnosetools zu kombinieren, z. B. dem JRockit Runtime Analyzer, der Teil von Oracle JRockit Mission Control ist (weitere Informationen finden Sie unter Verwenden von Oracle JRockit Mission Control Tools).
Erkennen von Deadlocks
Die Oracle JRockit JVM analysiert automatisch die Thread-Dump-Informationen und erkennt, ob sie zirkuläre (Deadlocks) oder blockierte Lock-Chains enthält.
Erkennen von Verarbeitungsengpässen
Um mehr als nur Deadlocks in Ihren Threads zu erkennen, müssen Sie mehrere aufeinanderfolgende Thread-Dumps erstellen. Auf diese Weise können Sie das Auftreten von Konflikten erkennen, wobei Mehrere Threads versuchen, dieselbe Sperre zu erhalten. Konflikte können zu langen offenen Sperrketten führen, die zwar keinen Deadlock aufweisen, aber die Leistung beeinträchtigen.
Wenn Sie (in einer Reihe aufeinanderfolgender Threaddumps) feststellen, dass ein oder mehrere Threads in Ihrer Anwendung vorübergehend nicht mehr auf die Aufhebung einer Sperre warten, haben Sie möglicherweise einen Grund, den Code Ihrer Java-Anwendung zu überprüfen, um festzustellen, ob die Synchronisierung (Serialisierung) erforderlich ist oder ob die Threads anders organisiert werden können.
Anzeigen des Laufzeitprofils einer Anwendung
Wenn Sie mehrere aufeinanderfolgende Thread-Dumps erstellen, erhalten Sie möglicherweise schnell einen Überblick darüber, welche Teile Ihrer Java-Anwendung am stärksten verwendet werden. Auf der Registerkarte "Threads" in der JRockit Management Console finden Sie jedoch detailliertere Informationen zur Arbeitsauslastung der verschiedenen Teile Ihrer Anwendung.