Es gibt einige Themen zu den Interna der JVM im Java Developer- Kursprogramm. Wir verstehen die Arbeitsmechanismen von Sammlungen, Bytecode, Garbage Collectors usw. Heute widmen wir Ihnen die Übersetzung eines ziemlich interessanten Artikels über Thread Dump. Was ist es, wie man es bekommt und wie man es benutzt.Möchten Sie lernen, wie Sie den Thread-Dump analysieren? Gehen Sie unter die Katze, um mehr darüber zu erfahren, wie Sie einen Thread-Dump in Java erhalten und was Sie später damit tun können.
Die meisten modernen Java-Anwendungen sind Multithread-Anwendungen. Multithreading kann die Funktionalität der Anwendung erheblich erweitern und gleichzeitig eine erhebliche Komplexität mit sich bringen.
In einer Single-Threaded-Anwendung können alle Ressourcen (gemeinsam genutzter Speicher, Eingabe- / Ausgabeoperationen usw.) ohne Synchronisierung verwendet werden, da Zu einem bestimmten Zeitpunkt verwendet nur ein Thread die Ressource.
Bei Multithread-Anwendungen muss ein Kompromiss zwischen der Komplikation des Programms und einer möglichen Leistungssteigerung gefunden werden, wenn mehrere Threads alle verfügbaren (häufig mehr als einen) Core-Prozessor (CPU) verwenden können. Wenn alles richtig gemacht wurde, können Sie mit Multithreading (formalisiert nach
Amdahls Gesetz ) eine signifikante Steigerung der Anwendungsleistung erzielen. Es muss jedoch beachtet werden, dass mehrere Streams gleichzeitig auf eine gemeinsam genutzte Ressource zugreifen können. In den meisten Fällen kapseln Frameworks wie Spring die Arbeit mit Threads und verbergen viele technische Details vor Benutzern. Bei der Verwendung moderner komplexer Frameworks kann jedoch etwas schief gehen, und wir als Benutzer werden auf schwer zu lösende Multithreading-Fehler stoßen.
Glücklicherweise ist Java mit einem speziellen Mechanismus ausgestattet, mit dem Informationen über den aktuellen Status aller Threads zu einem bestimmten Zeitpunkt abgerufen werden können - es handelt sich um einen Thread-Dump (eine Art Snapshot). In diesem Artikel erfahren Sie, wie Sie einen Thread-Dump für eine Anwendung mit realistischer Größe erstellen und diesen Dump analysieren.
Es wird davon ausgegangen, dass der Leser über grundlegende Informationen zur Multithread-Programmierung verfügt und sich der Probleme der Thread-Synchronisation und der Verwendung gemeinsam genutzter Ressourcen bewusst ist. Es wird jedoch nicht überflüssig sein, einige grundlegende Begriffe und Konzepte zu aktualisieren.
Grundlegende Terminologie
Auf den ersten Blick mögen Java-Thread-Dumps wie ein „chinesischer Buchstabe“ erscheinen. Die folgenden Konzepte sind der Schlüssel zum Verständnis. Im Allgemeinen wiederholen wir die Grundbegriffe des Multithreading, mit denen wir Dumps analysieren.
- Thread oder Thread ist eine diskrete Multithreading-Einheit, die von der Java Virtual Machine (JVM) verwaltet wird. JVM-Threads entsprechen Threads im Betriebssystem (OS) - native Threads, die den Codeausführungsmechanismus implementieren.
Jeder Thread hat eine eindeutige Kennung und einen eindeutigen Namen. Streams können "Dämonen" und "keine Dämonen" sein.
Das Programm wird beendet, wenn alle Nicht-Daemon-Threads beendet werden oder die Methode Runtime.exit aufgerufen wird . Arbeitende "Dämonen" haben keinen Einfluss auf den Abschluss des Programms. Das heißt, Die JVM wartet darauf, dass alle „Nicht-Dämonen“ finalisiert und heruntergefahren werden. Sie achten nicht auf „Nicht-Dämonen“.
Weitere Informationen finden Sie in der Dokumentation zur Thread-Klasse .
Ein Stream kann sich in einem der folgenden Zustände befinden:
- Lebendiger Thread oder "live" - ein Thread, der etwas Arbeit leistet (normaler Zustand).
- Blockierter Thread oder "blockiert" - Ein Thread, der versucht hat, in den synchronisierten Abschnitt einzutreten (synchronisiert), aber ein anderer Thread hat es bereits geschafft, zuerst in diesen Block einzutreten, und alle folgenden Threads, die versuchen, in denselben Block einzutreten, werden blockiert.
- Wartender Thread oder "Warten" - ein Thread, der die Wartemethode aufgerufen hat (möglicherweise mit einer Zeitüberschreitung) und jetzt darauf wartet, dass eine andere Methode notify oder nonifyAll für dasselbe Objekt ausführt.
Bitte beachten Sie, dass der Thread nicht als "wartend" betrachtet wird, wenn er "Warten mit einer Zeitüberschreitung" aufgerufen hat und diese Zeitüberschreitung abgelaufen ist. - Sleeping Thread oder "Sleeping" - ein Thread, der derzeit nicht ausgeführt wird, weil führte die Thread.sleep-Methode durch (Angabe der Dauer des "Schlafes").
- Monitor ist ein Mechanismus, der von der JVM verwendet wird, um Multithread-Zugriff auf ein einzelnes Objekt bereitzustellen. Der Mechanismus wird mit dem speziellen synchronisierten Schlüsselwort gestartet.
Jedes Objekt in Java hat einen Monitor, mit dem der Thread synchronisiert werden kann, d.h. Setzen Sie eine Sperre, die garantiert, dass kein anderer Thread Zugriff auf dieses Objekt erhält, bis die Sperre aufgehoben wird, d. h. Thread - Der Eigentümer der Sperre verlässt den synchronisierten Block nicht.
Weitere Informationen finden Sie im Abschnitt Synchronisierung (17.1) der Java Langauge Specification (JLS) .
- Deadlock ist eine Situation, in der ein Thread, z. B. A, eine Ressource blockiert. Er benötigt eine andere Ressource, die von einem anderen Thread blockiert wird, z. B. B. Stream B gibt diese Ressource nicht frei, weil Um einen bestimmten Vorgang abzuschließen, benötigt er eine Ressource, die von Thread A blockiert wird. Es stellt sich heraus, dass Thread A darauf wartet, dass die Ressource von Thread B entsperrt wird, der darauf wartet, dass eine andere Ressource von Thread A entsperrt wird. Daher warten die Threads aufeinander. Infolgedessen hängt das gesamte Programm und wartet darauf, dass die Threads irgendwie entsperrt werden und weiterarbeiten. In einem Deadlock können sich viele Threads befinden. Dieses Problem ist als "Problem der Speisephilosophen" bekannt.

- Livelock ist eine Situation, in der Thread A Thread B zwingt, eine Aktion auszuführen, was wiederum dazu führt, dass Thread A die anfängliche Aktion ausführt, was wiederum die Aktion von Thread B bewirkt. Eine zyklische Abhängigkeit wird erhalten. Dies kann man sich als einen Hund vorstellen, der seinem Schwanz nachläuft. Ähnlich wie bei Deadlock macht das Programm in einer Livelock-Situation keine Fortschritte, d. H. führt keine nützliche Aktion aus, in dieser Situation werden die Threads jedoch nicht blockiert.
Die vorgestellte Terminologie ist nicht erschöpfend für die Beschreibung der Welt des Multithreading, aber dies reicht aus, um mit der Analyse von Thread-Dumps zu beginnen.
Weitere Informationen finden Sie in folgenden Quellen:
Abschnitt 17 der JLS- und
Java-Parallelität in der PraxisMit diesen einfachen Konzepten zum Ablauf in Java können wir eine Testanwendung erstellen. Für diese Anwendung kompilieren wir den Thread-Dump. Wir werden den resultierenden Speicherauszug analysieren und nützliche Informationen über die aktuellen Anwendungsabläufe extrahieren.
Beispielprogramm erstellen
Vor dem Erstellen eines Thread-Dumps müssen wir eine Java-Anwendung entwickeln. Das traditionelle "Hallo Welt!" Für unseren Zweck zu einfach, und ein mittelgroßer Speicherauszug der Anwendung ist möglicherweise zu kompliziert, um dies zu demonstrieren. Basierend darauf werden wir eine ziemlich einfache Anwendung erstellen, in der zwei Threads erstellt werden. Und die Fäden geraten ins Stocken:
public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceB, resourceA)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized(firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized(secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } }
Dieses Programm erstellt zwei Ressourcen: resourceA und resourceB und startet zwei Threads: threadLockingResourceAFirst und threadLockingResourceBFirst, die sich gegenseitig die Ressourcen blockieren.
Die Ursache für einen Deadlock ist eine "Kreuz" -Blockierung von Ressourcen durch Threads.
Der Grund für das Auftreten eines Deadlocks ist ein Versuch, Ressourcen "gegenseitig" zu beschlagnahmen, d.h. threadLockingResourceAFirst-Thread erfasst resourceA-Ressource, threadLockingResourceBFirst-Thread erfasst resourceB-Ressource. Danach versucht threadLockingResourceAFirst, ohne seine Ressource freizugeben, resourceB zu greifen, und threadLockingResourceBFirst, ohne seine Ressource freizugeben, versucht, resourceA zu greifen. Infolgedessen werden Threads blockiert. Eine Verzögerung von 1s wurde hinzugefügt, um die Blockierung zu gewährleisten. Threads warten auf die Freigabe der erforderlichen Ressourcen, dies wird jedoch niemals passieren.
Die Ausgabe des Programms sieht folgendermaßen aus (die Zahlen nach java.lang.Object @ sind bei jedem Start unterschiedlich):
Thread-0: locked resource -> java.lang.Object@149bc794 Thread-1: locked resource -> java.lang.Object@17c10009
Nach der Ausgabe dieser Nachrichten sieht das Programm so aus, als würde es ausgeführt (der Prozess, der dieses Programm ausführt, ist nicht abgeschlossen), während das Programm keine Arbeit ausführt. So sieht Deadlock in der Praxis aus. Um das Problem zu lösen, müssen wir manuell einen Profildump erstellen und den Status der Threads analysieren.
Thread Dump Generation
In der Praxis kann ein Java-Programm beim Erstellen eines Thread-Dumps abstürzen. In einigen Fällen (z. B. bei Deadlocks) wird das Programm jedoch nicht beendet und der Thread-Dump wird nicht erstellt, sondern hängt nur. Um einen Speicherauszug solcher blockierter Programme zu erstellen, müssen Sie zunächst die Kennung des Programmprozesses herausfinden, d. H. Prozess-ID (PID). Dazu können Sie das Dienstprogramm JVM Process Status (JPS) verwenden, das ab Version 7 Teil des Java Development Kit (JDK) ist. Um die Prozess-PID unseres blockierten Programms zu ermitteln, führen wir einfach jps im Terminal (Windows oder Linux) aus:
$ jps 11568 DeadlockProgram 15584 Jps 15636
Die erste Spalte ist die Kennung der lokalen virtuellen Maschine (lokale VM-ID, d. H. Lvmid) für den ausgeführten Java-Prozess. Im Kontext der lokalen JVM verweist lvmid auf die PID des Java-Prozesses.
Es ist zu beachten, dass dieser Wert wahrscheinlich vom obigen Wert abweicht. Die zweite Spalte ist der Name der Anwendung, der auf den Namen der Hauptklasse, der JAR-Datei oder gleich "Unbekannt" verweisen kann. Es hängt alles davon ab, wie die Anwendung gestartet wurde.
In unserem Fall ist der Anwendungsname DeadlockProgram der Name der Hauptklassen, die beim Start des Programms gestartet wurden. Im obigen Beispiel PID des Programms 11568 reichen diese Informationen aus, um einen Thread-Dump zu generieren. Um den
Speicherauszug zu generieren, verwenden wir das Dienstprogramm
jstack , das Teil des JDK ist, ab Version 7. Um den
Speicherauszug abzurufen , übergeben wir
die PID unseres Programms an
jstack und geben das Flag -l an (Erstellen einer langen Liste). Die Ausgabe des Dienstprogramms wird in eine Textdatei umgeleitet, d. H. thread_dump.txt:
jstack -l 11568 > thread_dump.txt
Die resultierende Datei thread_dump.txt enthält den Thread-Dump unseres blockierten Programms und wichtige Informationen zur Diagnose der Ursachen für Deadlocks.
Wenn das JDK bis Version 7 verwendet wird, können Sie zum Generieren eines Speicherauszugs das Linux-Dienstprogramm verwenden -
kill mit dem Flag -3. Wenn Sie kill -3 aufrufen, sendet das Programm ein SIGQUIT-Signal.
In unserem Fall sieht der Anruf folgendermaßen aus:
kill -3 11568
Einfache Thread-Dump-Analyse
Wenn Sie die Datei thread_dump.txt öffnen, sehen Sie ungefähr Folgendes:
2018-06-19 16:44:44
Vollständiger Thread-Dump Java HotSpot (TM) 64-Bit-Server-VM (10.0.1 + 10 gemischter Modus):
Threads Klasse SMR Info:
_java_thread_list = 0x00000250e5488a00, Länge = 13, Elemente = {
0x00000250e4979000, 0x00000250e4982800, 0x00000250e52f2800, 0x00000250e4992800,
0x00000250e4995800, 0x00000250e49a5800, 0x00000250e49ae800, 0x00000250e5324000,
0x00000250e54cd800, 0x00000250e54cf000, 0x00000250e54d1800, 0x00000250e54d2000,
0x00000250e54d0800
}}
"Reference Handler" # 2 Daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 wartet auf Bedingung [0x000000b82a9ff000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
unter java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
unter java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
at java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
Gesperrte besitzbare Synchronisierer:
- Keine
"Finalizer" # 3 Daemon prio = 8 os_prio = 1 tid = 0x00000250e4982800 nid = 0x2a54 in Object.wait () [0x000000b82aaff000]
java.lang.Thread.State: WAITING (auf dem Objektmonitor)
at java.lang.Object.wait (java.base@10.0.1/Native Method)
- Warten auf <0x0000000089509410> (eine java.lang.ref.ReferenceQueue $ Lock)
unter java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
- Warten auf erneutes Sperren in wait () <0x0000000089509410> (a java.lang.ref.ReferenceQueue $ Lock)
unter java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 172)
at java.lang.ref.Finalizer $ FinalizerThread.run (java.base@10.0.1/Finalizer.java: 216)
Gesperrte besitzbare Synchronisierer:
- Keine
"Signal Dispatcher" # 4 Daemon prio = 9 os_prio = 2 tid = 0x00000250e52f2800 nid = 0x2184 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Gesperrte besitzbare Synchronisierer:
- Keine
"Listener anhängen" # 5 Daemon prio = 5 os_prio = 2 tid = 0x00000250e4992800 nid = 0x1624 wartet auf Bedingung [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Gesperrte besitzbare Synchronisierer:
- Keine
"C2 CompilerThread0" # 6 Daemon prio = 9 os_prio = 2 tid = 0x00000250e4995800 nid = 0x4198 wartet auf Bedingung [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Keine Kompilierungsaufgabe
Gesperrte besitzbare Synchronisierer:
- Keine
"C2 CompilerThread1" # 7 Daemon prio = 9 os_prio = 2 tid = 0x00000250e49a5800 nid = 0x3b98 wartet auf Bedingung [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Keine Kompilierungsaufgabe
Gesperrte besitzbare Synchronisierer:
- Keine
"C1 CompilerThread2" # 8 Daemon prio = 9 os_prio = 2 tid = 0x00000250e49ae800 nid = 0x1a84 wartet auf Bedingung [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Keine Kompilierungsaufgabe
Gesperrte besitzbare Synchronisierer:
- Keine
"Sweeper-Thread" # 9 Daemon prio = 9 os_prio = 2 tid = 0x00000250e5324000 nid = 0x5f0 ausführbar [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Gesperrte besitzbare Synchronisierer:
- Keine
"Service Thread" # 10 Daemon prio = 9 os_prio = 0 tid = 0x00000250e54cd800 nid = 0x169c ausführbar [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Gesperrte besitzbare Synchronisierer:
- Keine
"Common-Cleaner" # 11 Daemon prio = 8 os_prio = 1 tid = 0x00000250e54cf000 nid = 0x1610 in Object.wait () [0x000000b82b2fe000]
java.lang.Thread.State: TIMED_WAITING (auf dem Objektmonitor)
at java.lang.Object.wait (java.base@10.0.1/Native Method)
- Warten auf <0x000000008943e600> (eine java.lang.ref.ReferenceQueue $ Lock)
unter java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
- Warten auf erneutes Sperren in wait () <0x000000008943e600> (a java.lang.ref.ReferenceQueue $ Lock)
unter jdk.internal.ref.CleanerImpl.run (java.base@10.0.1/CleanerImpl.java: 148)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
unter jdk.internal.misc.InnocuousThread.run (java.base@10.0.1/InnocuousThread.java: 134)
Gesperrte besitzbare Synchronisierer:
- Keine
"Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec wartet auf Monitoreintrag [0x000000b82b4ff000]
java.lang.Thread.State: BLOCKED (auf dem Objektmonitor)
bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- Warten auf das Sperren von <0x00000000894465b0> (ein java.lang.Object)
- gesperrt <0x00000000894465a0> (ein java.lang.Object)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Gesperrte besitzbare Synchronisierer:
- Keine
"Thread-1" # 13 prio = 5 os_prio = 0 tid = 0x00000250e54d2000 nid = 0x415c wartet auf Monitoreintrag [0x000000b82b5ff000]
java.lang.Thread.State: BLOCKED (auf dem Objektmonitor)
bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- Warten auf das Sperren von <0x00000000894465a0> (ein java.lang.Object)
- gesperrt <0x00000000894465b0> (ein java.lang.Object)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Gesperrte besitzbare Synchronisierer:
- Keine
"DestroyJavaVM" # 14 prio = 5 os_prio = 0 tid = 0x00000250e54d0800 nid = 0x2b8c wartet auf Bedingung [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Gesperrte besitzbare Synchronisierer:
- Keine
"VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 ausführbar
"GC Thread # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c ausführbar
"GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 ausführbar
"GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 ausführbar
"GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 ausführbar
"G1 Hauptmarker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 ausführbar
"G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 ausführbar
"G1 Refine # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c ausführbar
"G1 Refine # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 ausführbar
"G1 Refine # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 ausführbar
"G1 Refine # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 ausführbar
"G1 Young RemSet Sampling" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 runnable
"VM Periodic Task Thread" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 wartet auf Bedingung
Globale JNI-Referenzen: 2
Es wurde ein Deadlock auf Java-Ebene gefunden:
===============================
"Thread-0":
Warten auf Sperren des Monitors 0x00000250e4982480 (Objekt 0x00000000894465b0, ein java.lang.Object),
welches von "Thread-1" gehalten wird
"Thread-1":
Warten auf Sperren des Monitors 0x00000250e4982380 (Objekt 0x00000000894465a0, ein java.lang.Object),
welches von "Thread-0" gehalten wird
Java-Stack-Informationen für die oben aufgeführten Threads:
================================================== =
"Thread-0":
bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- Warten auf das Sperren von <0x00000000894465b0> (ein java.lang.Object)
- gesperrt <0x00000000894465a0> (ein java.lang.Object)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
"Thread-1":
bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- Warten auf das Sperren von <0x00000000894465a0> (ein java.lang.Object)
- gesperrt <0x00000000894465b0> (ein java.lang.Object)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
1 Deadlock gefunden.
Einführende Informationen
Obwohl diese Datei auf den ersten Blick zu kompliziert und verwirrend erscheint, ist es in Wirklichkeit recht einfach, wenn Sie sie Schritt für Schritt in Teile zerlegen.
Die erste Zeile gibt den Zeitpunkt an, zu dem der Speicherauszug erstellt wurde, die zweite - Diagnoseinformationen über die JVM, auf der der Speicherauszug empfangen wurde:
2018-06-19 16:44:44 Full thread dump Java HotSpot(TM) 64-Bit Server VM (10.0.1+10 mixed mode):
In diesem Abschnitt gibt es keine Flussinformationen. Hier wird der allgemeine Kontext des Systems festgelegt, in dem der Speicherauszug gesammelt wurde.
Allgemeine Flussinformationen
Der folgende Abschnitt enthält Informationen zu den Threads, die zum Zeitpunkt der Speicherauszugserfassung im System ausgeführt wurden:
Threads Klasse SMR Info:
_java_thread_list = 0x00000250e5488a00, Länge = 13, Elemente = {
0x00000250e4979000, 0x00000250e4982800, 0x00000250e52f2800, 0x00000250e4992800,
0x00000250e4995800, 0x00000250e49a5800, 0x00000250e49ae800, 0x00000250e5324000,
0x00000250e54cd800, 0x00000250e54cf000, 0x00000250e54d1800, 0x00000250e54d2000,
0x00000250e54d0800
}}
Der folgende Abschnitt listet auf:
SMR-Informationen (Safe Memory Reclamation)Es enthält Informationen zu Threads außerhalb der JVM, d. H. Dies sind keine Threads für virtuelle Maschinen oder Garbage Collection-Threads. Wenn Sie sich die Adressen dieser Threads ansehen, werden Sie feststellen, dass sie dem Wert von
tid entsprechen - der "natürlichen, eisernen" (nativen) Adresse im Betriebssystem und nicht der Thread-ID.
Ellipsen werden verwendet, um redundante Informationen auszublenden:
"Referenzhandler" # 2 ... tid = 0x00000250e4979000 ...
"Finalizer" # 3 ... tid = 0x00000250e4982800 ...
"Signal Dispatcher" # 4 ... tid = 0x00000250e52f2800 ...
"Listener anhängen" # 5 ... tid = 0x00000250e4992800 ...
"C2 CompilerThread0" # 6 ... tid = 0x00000250e4995800 ...
"C2 CompilerThread1" # 7 ... tid = 0x00000250e49a5800 ...
"C1 CompilerThread2" # 8 ... tid = 0x00000250e49ae800 ...
"Kehrfaden" # 9 ... tid = 0x00000250e5324000 ...
"Service Thread" # 10 ... tid = 0x00000250e54cd800 ...
"Common-Cleaner" # 11 ... tid = 0x00000250e54cf000 ...
"Thread-0" # 12 ... tid = 0x00000250e54d1800 ...
"Thread-1" # 13 ... tid = 0x00000250e54d2000 ...
"DestroyJavaVM" # 14 ... tid = 0x00000250e54d0800 ...
Streams
Direkt nach dem SMR-Block befindet sich eine Liste von Threads. Der erste Thread auf unserer Liste ist der Referenzhandler:
"Reference Handler" # 2 Daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 wartet auf Bedingung [0x000000b82a9ff000]
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
unter java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
unter java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
at java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
Gesperrte besitzbare Synchronisierer:
- Keine
Kurze Beschreibung des Streams
Die erste Zeile für jeden Thread enthält eine allgemeine Beschreibung. Die Beschreibung enthält folgende Elemente:
Abschnitt | Beispiel | Beschreibung |
---|
Name | "Referenzhandler" | Vom Menschen lesbarer Streamname. Der Name kann durch Aufrufen der setName- Methode des Thread- Objekts angegeben werden. Und rufen Sie getName an |
ID | # 2 | Eine eindeutige ID, die jedem Objekt der Thread- Klasse zugewiesen wird. ID wird für Threads im System generiert. Der Anfangswert ist 1. Jedem neu erstellten Thread wird eine eigene ID zugewiesen, die zuvor um 1 erhöht wurde. Diese schreibgeschützte Thread-Eigenschaft kann mit der Funktion getId eines Objekts der Thread- Klasse abgerufen werden. |
Daemon-Status | Daemon | Die Flagge ist ein Zeichen dafür, dass der Thread ein Dämon ist. Wenn es sich um einen Dämon handelt, wird die Flagge gesetzt. Beispielsweise ist Thread -0 kein Daemon. |
Priorität | Prio = 10 | Die numerische Priorität des Java-Streams. Beachten Sie, dass diese Priorität nicht unbedingt der Priorität des zugeordneten Threads im Betriebssystem entspricht. Um die Priorität festzulegen, können Sie Verwenden Sie die setPriority- Methode eines Objekts der Klasse Thread und, um abzurufen getPriority- Methode. |
OS-Thread-Priorität | os_prio = 2 | Prioritäts-Thread im Betriebssystem. Diese Priorität kann von der dem verknüpften Java-Thread zugewiesenen Priorität abweichen. |
Adresse | tid = 0x00000250e4979000 | Die Adresse des Java-Streams. Diese Adresse ist ein Zeiger auf das native JNI- Objekt (Java Native Interface) der Thread- Klasse (ein C ++ - Thread- Objekt, das über JNI mit dem Java-Thread verbunden ist). Dieser Wert wird erhalten, indem ein Zeiger darauf geworfen wird (das C ++ - Objekt, das diesem Java-Thread zugeordnet ist) in Ganzzahl. Siehe Zeile 879 in hotspot / share / runtime / thread.cpp :
st-> print ("tid =" INTPTR_FORMAT "", p2i (this));
Obwohl der Schlüssel für dieses Objekt ( tid ) wie eine Stream-ID aussehen kann, Tatsächlich ist dies die Adresse des mit dem JNI C ++ - Thread verbundenen Objekts, und dies ist nicht der Wert, der Gibt die getId- Methode des Java- Threads zurück . |
OS-Thread-ID | nid = 0x3c28 | Die eindeutige Kennung des Betriebssystem-Threads, an den der Java-Thread gebunden ist. Dieser Wert wird mit folgendem Code ausgegeben: Zeile 42 in hotspot / share / runtime / osThread.cpp :
st-> print ("nid = 0x% x", thread_id ());
|
Status | Warten unter der Bedingung | Vom Menschen lesbarer Status des aktuellen Threads. In dieser Zeile werden zusätzliche Informationen zum einfachen Status des Streams angezeigt (siehe unten) wird verwendet, um zu verstehen, was der Thread tun würde (d. h. ob der Thread versucht hat, eine Sperre zu erhalten oder darauf warten, dass die Entsperrbedingung erfüllt ist). |
Letzter bekannter Java-Stapelzeiger | [0x000000b82a9ff000] | Der letzte bekannte Stapelzeiger (SP), der diesem Stream zugeordnet ist. Dieser Wert wird mit nativem C ++ - Code erhalten, der mit Java-Code unter Verwendung von JNI gemischt wird. Der Wert wird von der Funktion last_Java_sp () zurückgegeben . Zeile 2886 in hotspot / share / runtime / thread.cpp :
st-> print_cr ("[" INTPTR_FORMAT "]",
(intptr_t) last_Java_sp () & ~ right_n_bits (12));
Für einfache Thread-Dumps sind diese Informationen fast unbrauchbar. In komplexen Fällen kann SP jedoch verwendet werden, um Schlösser zu verfolgen. |
Stream-Status
Die zweite Zeile ist der aktuelle Status des Streams. Mögliche Stream-Zustände sind in Aufzählung aufgeführt:
Thread.State :
NEU
RUNNABLE
BLOCKIERT
WARTEN
TIMED_WAITING
BEENDET
Weitere Informationen finden Sie in der
Dokumentation .
Thread-Stack-Trace
Der nächste Abschnitt enthält die Stapelverfolgung des Streams zum Zeitpunkt des Dumps. Diese Stapelverfolgung ist einer Stapelverfolgung sehr ähnlich, die von einer nicht erfassten Ausnahme ausgelöst wird. Und es enthält die Namen von Klassen und Zeichenfolgen, die zum Zeitpunkt der Erstellung des Dumps ausgeführt wurden. Im Fall des Reference Handler-Streams sehen wir nichts Interessantes.
Die Thread-02-Thread-Ablaufverfolgung unterscheidet sich jedoch von der Standard-Ablaufverfolgung:
"Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec wartet auf Monitoreintrag [0x000000b82b4ff000]
java.lang.Thread.State: BLOCKED (auf dem Objektmonitor)
bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- Warten auf das Sperren von <0x00000000894465b0> (ein java.lang.Object)
- gesperrt <0x00000000894465a0> (ein java.lang.Object)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Gesperrte besitzbare Synchronisierer:
- Keine
In der Ablaufverfolgung sehen wir, dass Informationen zur Sperre hinzugefügt wurden. Dieser Thread erwartet eine Sperre für das Objekt mit der Adresse 0x00000000894465b0 (Objekttyp java.lang.Object). Darüber hinaus enthält der Thread selbst die Sperre mit der Adresse 0x00000000894465a0 (auch ein java.lang.Object). Diese Informationen werden uns später für die Diagnose eines Deadlocks nützlich sein.
Erfasste Synchronisationsprimitive (Ownable Synchronizer)
Der letzte Abschnitt listet die vom Stream erfassten Synchronisationsprimitive auf. Dies sind Objekte, mit denen Threads synchronisiert werden können, z. B. Sperren.
Gemäß der offiziellen Java-Dokumentation ist
Ownable Synchronizer der Nachkomme von
AbstractOwnableSynchronizer (oder seiner Unterklasse), der ausschließlich vom Stream zu Synchronisationszwecken erfasst werden kann.
ReentrantLock und
Schreibsperre , jedoch nicht die
Lesesperre der
ReentrantReadWriteLock- Klasse, sind zwei gute Beispiele für solche "besitzbaren Synchronisierer", die von der Plattform angeboten werden.
Weitere Informationen zu diesem Thema finden Sie hier
Post .
JVM-Threads
Der nächste Abschnitt des Speicherauszugs enthält Informationen zu technischen JVM-Threads, die nicht Teil der Anwendung sind und Betriebssystem-Threads zugeordnet sind. Weil Diese Streams funktionieren außerhalb der Anwendung. Sie haben keine Stream-IDs. Am häufigsten sind dies Garbage Collector-Threads und andere technische JVM-Threads:
"VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 ausführbar
"GC Thread # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c ausführbar
"GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 ausführbar
"GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 ausführbar
"GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 ausführbar
"G1 Hauptmarker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 ausführbar
"G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 ausführbar
"G1 Refine # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c ausführbar
"G1 Refine # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 ausführbar
"G1 Refine # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 ausführbar
"G1 Refine # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 ausführbar
"G1 Young RemSet Sampling" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 runnable
"VM Periodic Task Thread" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 wartet auf Bedingung
JNI Global Links
Dieser Abschnitt gibt die Anzahl der globalen Referenzen an, die von der JVM über die JNI verwendet werden. Diese Links werden vom Garbage Collector nicht bereitgestellt und können unter bestimmten Umständen einen Speicherverlust verursachen.
Globale JNI-Referenzen: 2
In den meisten einfachen Fällen werden diese Informationen nicht verwendet. Die Bedeutung globaler Referenzen muss jedoch verstanden werden. Weitere Informationen finden Sie in diesem
Beitrag .
Deadlocked Threads
Der letzte Abschnitt enthält Informationen zu gefundenen Deadlocks.
Wenn diese nicht gefunden werden, ist der Abschnitt leer. Weil Wir haben speziell eine Anwendung mit Schlössern entwickelt, in unserem Fall ist dieser Abschnitt. Während des Dumpings wurde eine Sperre festgestellt, die folgende Meldung enthält:
Es wurde ein Deadlock auf Java-Ebene gefunden:
===============================
"Thread-0":
Warten auf Sperren des Monitors 0x00000250e4982480 (Objekt 0x00000000894465b0, ein java.lang.Object),
welches von "Thread-1" gehalten wird
"Thread-1":
Warten auf Sperren des Monitors 0x00000250e4982380 (Objekt 0x00000000894465a0, ein java.lang.Object),
welches von "Thread-0" gehalten wird
Java-Stack-Informationen für die oben aufgeführten Threads:
================================================== =
"Thread-0":
bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- Warten auf das Sperren von <0x00000000894465b0> (ein java.lang.Object)
- gesperrt <0x00000000894465a0> (ein java.lang.Object)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
"Thread-1":
bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- Warten auf das Sperren von <0x00000000894465a0> (ein java.lang.Object)
- gesperrt <0x00000000894465b0> (ein java.lang.Object)
unter java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
1 Deadlock gefunden.
Der erste Unterabschnitt beschreibt das Deadlock-Szenario:
Thread-0 erwartet, dass der Monitor erfasst werden kann (dies ist ein Zugriff auf den
synchronisierten Block
(secondResource) in unserer Anwendung). Gleichzeitig hält dieser Thread einen Monitor, der versucht, den Thread-1 zu erfassen (dies
greift auf dasselbe Codefragment zu:
synchronized (secondResource) ) in unserer Bewerbung).
Dieses kreisförmige Schloss wird auch als
Deadlock bezeichnet . Im Bild unten
Diese Situation wird in grafischer Form dargestellt:

Im zweiten Unterabschnitt wird die Stapelverfolgung für beide blockierten Threads angegeben.
Mit dieser Stapelverfolgung können wir den Betrieb jedes Threads verfolgen, bis eine Sperre auftritt.
In unserem Fall, wenn wir uns die Zeile ansehen:
Bei DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34) sehen wir den Problemteil des Codes:
printLockedResource (secondResource);
Diese Zeile ist die erste Zeile des synchronisierten Blocks, die den Grund für die Sperre darstellt, und gibt an, dass die Synchronisierung auf der zweiten Ressource der Grund für die gegenseitige Sperre ist. Um Abhilfe zu schaffen, müssen wir sicherstellen, dass beide Threads dieselbe Synchronisationsreihenfolge für die Ressourcen resourceA und resourceB haben. Wenn wir dies tun, kommen wir zu der folgenden Anwendung:
public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized (firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized (secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } }
Diese Anwendung wird ohne Verriegelung beendet, und als Ergebnis erhalten wir die folgende Ausgabe (beachten Sie, dass sich die Adressen der Object-Klasse geändert haben):
Thread-0: gesperrte Ressource -> java.lang.Object@1ad895d1
Thread-0: gesperrte Ressource -> java.lang.Object@6e41d7dd
Thread-1: gesperrte Ressource -> java.lang.Object@1ad895d1
Thread-1: gesperrte Ressource -> java.lang.Object@6e41d7dd
, , thread dump, . ( deadlock-). , .
Thread Dump-
.
JVM . ( , ).
.
- — Thread Dump Analyzers (TDAs). Java thread dump- - , . , . , .
TDA:
. .
Fazit
Thread dump- — Java-, . , .
deadlock, . . , — .
, Java- thread dump-. , .
, thread dump — « » , , Java-.
Java . , deadlock- ., .