Il y a pas mal de sujets sur les internes de la JVM dans le programme de cours Java Developer . Nous comprenons les mécanismes de fonctionnement des collections, bytecode, garbage collectors, etc. Aujourd'hui, nous offrons votre attention sur la traduction d'un article assez intéressant sur le vidage de threads. Qu'est-ce que c'est, comment l'obtenir et comment l'utiliser.Vous voulez apprendre à analyser le vidage des threads? Allez sous le chat pour en savoir plus sur la façon d'obtenir un vidage de threads en Java et que faire avec plus tard.
La plupart des applications Java modernes sont multithread. Le multithreading peut étendre considérablement les fonctionnalités de l'application, tout en introduisant une complexité importante.
Dans une application monothread, toutes les ressources (mémoire partagée, opérations d'entrée / sortie, etc.) peuvent être utilisées sans synchronisation, car à un moment donné, un seul thread utilise la ressource.
Dans le cas d'applications multi-thread, il est nécessaire de trouver un compromis entre compliquer le programme et une augmentation possible des performances, lorsque plusieurs threads peuvent utiliser tous les processeurs (CPU) de base disponibles (souvent plus d'un). Si tout est fait correctement, alors en utilisant le multithreading (formalisé dans
la loi d'Amdahl ), vous pouvez obtenir une augmentation significative des performances de l'application. Cependant, il faut se rappeler de fournir un accès simultané de plusieurs flux à une ressource partagée. Dans la plupart des cas, les frameworks tels que Spring encapsulent le travail avec les threads et masquent de nombreux détails techniques aux utilisateurs. Cependant, dans le cas de l'utilisation de cadres complexes modernes, quelque chose peut mal tourner, et nous, en tant qu'utilisateurs, rencontrerons des bogues multithreads difficiles à résoudre.
Heureusement, Java est équipé d'un mécanisme spécial pour obtenir des informations sur l'état actuel de tous les threads à un moment donné - il s'agit d'un vidage de thread (une sorte d'instantané). Dans cet article, nous apprendrons comment obtenir un vidage de thread pour une application de taille réaliste et comment analyser ce vidage.
Il est supposé que le lecteur dispose d'informations de base sur la programmation multithread et qu'il est conscient des problèmes de synchronisation des threads et d'utilisation des ressources partagées. Néanmoins, il ne sera pas superflu de rafraîchir certains termes et concepts de base.
Terminologie de base
À première vue, les vidages de threads Java peuvent sembler être une «lettre chinoise», les concepts suivants sont essentiels pour la comprendre. En général, répétons les termes de base du multithreading, que nous utiliserons pour analyser les vidages.
- Le thread ou le thread est une unité multithread discrète gérée par la machine virtuelle Java (JVM). Les threads JVM correspondent aux threads du système d'exploitation (OS) - threads natifs, qui implémentent le mécanisme d'exécution de code.
Chaque thread a un identifiant et un nom uniques. Les flux peuvent être des "démons" et "pas des démons".
Le programme se termine lorsque tous les threads non démon se terminent ou lorsque la méthode Runtime.exit est appelée . Les «démons» qui travaillent n'affectent pas l'achèvement du programme. C'est-à-dire La JVM attend que tous les «non-démons» soient finalisés et fermés, ils ne font pas attention aux «non-démons».
Pour plus d'informations, consultez la documentation de la classe Thread .
Un flux peut se trouver dans l'un des états suivants:
- Fil vivant ou «en direct» - un fil qui fonctionne (état normal).
- Thread bloqué ou «bloqué» - un thread qui a tenté d'entrer dans la section synchronisée (synchronisé), mais un autre thread a déjà réussi à entrer ce bloc en premier, et tous les threads suivants qui tentent d'entrer dans le même bloc sont bloqués.
- Fil d'attente ou «en attente» - un fil qui a appelé la méthode d' attente (éventuellement avec un délai d'expiration) et attend maintenant qu'une autre méthode s'exécute notifie ou nonifieAll sur le même objet.
Veuillez noter que le thread n'est pas considéré comme «en attente» s'il a appelé wait with a timeout et que ce délai a expiré. - Fil dormant ou "dormant" - un fil qui n'est pas en cours d'exécution, car effectué la méthode Thread.sleep (indiquant la durée du "sommeil").
- Monitor est un mécanisme utilisé par la JVM pour fournir un accès multithread à un seul objet. Le mécanisme est démarré à l'aide du mot clé synchronisé spécial.
Chaque objet en Java a un moniteur avec lequel le thread peut être synchronisé, c'est-à-dire définir un verrou, ce qui garantit qu'aucun autre thread n'accédera à cet objet tant que le verrou n'est pas libéré, c'est-à-dire thread - le propriétaire du verrou ne quittera pas le bloc synchronisé .
Voir la section Synchronisation (17.1) de la spécification Java Langauge (JLS) pour plus d'informations .
- Le blocage est une situation dans laquelle un thread, par exemple A, bloque une ressource, il a besoin d'une autre ressource qui est bloquée par un autre thread, par exemple B. Le flux B ne libère pas cette ressource, car Pour terminer une certaine opération, il a besoin d'une ressource qui est bloquée par le thread A. Il s'avère que le thread A attend que la ressource soit déverrouillée par le thread B, qui attend qu'une autre ressource soit déverrouillée par le thread A. Et, par conséquent, les threads attendent les uns les autres. Par conséquent, le programme entier se bloque et attend que les threads se déverrouillent et continuent de fonctionner. Il peut y avoir plusieurs threads dans une impasse. Ce problème est bien connu sous le nom de «problème des philosophes de la restauration» .

- Livelock est une situation où le thread A force le thread B à effectuer une action, ce qui oblige le thread A à effectuer l'action initiale, ce qui provoque à nouveau l'action du thread B. Une dépendance cyclique est obtenue. Cela peut être imaginé comme un chien qui court après sa queue. Comme pour Deadlock , dans une situation Livelock, le programme ne progresse pas, c'est-à-dire n'effectue pas d'action utile, cependant, dans cette situation, les threads ne sont pas bloqués.
La terminologie présentée n'est pas exhaustive pour décrire le monde du multithreading, mais cela suffit pour commencer à analyser les vidages de threads.
Des informations plus détaillées peuvent être trouvées dans ces sources:
Section 17 du JLS et de
la concurrence Java en pratiqueEn utilisant ces concepts simples sur le flux en Java, nous pouvons créer une application de test. Pour cette application, nous compilerons le vidage des threads. Nous analyserons le vidage résultant et extrairons des informations utiles sur les flux d'application actuels.
Création d'un exemple de programme
Avant de créer un vidage de thread, nous devons développer une application Java. Le traditionnel "bonjour, le monde!" trop simple pour notre objectif, et un vidage de taille moyenne de l'application peut être trop compliqué à démontrer. Sur cette base, nous allons créer une application assez simple dans laquelle deux threads sont créés. Et les fils tombent dans l'impasse:
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); } } }
Ce programme crée deux ressources: resourceA et resourceB et démarre deux threads: threadLockingResourceAFirst et threadLockingResourceBFirst, qui se bloquent mutuellement les ressources.
La cause du blocage est un blocage «croisé» des ressources par les threads.
La raison de l'occurrence de l'impasse est une tentative de saisie "mutuelle" des ressources, c'est-à-dire Le thread threadLockingResourceAFirst capture la ressource resourceA, le thread threadLockingResourceBFirst capture la ressource resourceB. Après cela, threadLockingResourceAFirst, sans libérer sa ressource, tente de capturer resourceB, et threadLockingResourceBFirst, sans libérer sa ressource, tente de capturer resourceA. Par conséquent, les threads sont bloqués. Un délai de 1 s a été ajouté pour garantir le blocage. Les threads attendent la libération des ressources nécessaires, mais cela ne se produira jamais.
La sortie du programme sera comme ceci (les chiffres après java.lang.Object @ seront différents pour chaque lancement):
Thread-0: locked resource -> java.lang.Object@149bc794 Thread-1: locked resource -> java.lang.Object@17c10009
Après la sortie de ces messages, le programme aura l'air d'être en cours d'exécution (le processus exécutant ce programme n'est pas terminé), tandis que le programme ne fait aucun travail. Voici à quoi ressemble l'impasse dans la pratique. Pour résoudre le problème, nous devons créer manuellement un vidage de la bande de roulement et analyser l'état des threads.
Génération de vidage de thread
En pratique, un programme Java peut se bloquer lors de la création d'un vidage de thread. Cependant, dans certains cas (par exemple, dans le cas de blocages), le programme ne se termine pas et le vidage de thread ne se crée pas, il se bloque simplement. Pour créer un vidage de ces programmes bloqués, vous devez d'abord trouver l'identifiant du processus du programme, c'est-à-dire ID de processus (PID). Pour ce faire, vous pouvez utiliser l'utilitaire JVM Process Status (JPS) qui, à partir de la version 7, fait partie du kit de développement Java (JDK). Pour trouver le processus PID de notre programme bloqué, nous exécutons simplement jps dans le terminal (Windows ou Linux):
$ jps 11568 DeadlockProgram 15584 Jps 15636
La première colonne est l'identifiant de la machine virtuelle locale (Local VM ID, c'est-à-dire lvmid) pour le processus Java en cours d'exécution. Dans le contexte de la JVM locale, lvmid pointe vers le PID du processus Java.
Il convient de noter que cette valeur est susceptible de différer de la valeur ci-dessus. La deuxième colonne est le nom de l'application, qui peut pointer vers le nom de la classe principale, du fichier jar ou égal à "Inconnu". Tout dépend de la façon dont l'application a été lancée.
Dans notre cas, le nom de l'application DeadlockProgram est le nom des principales classes qui ont été lancées au démarrage du programme. Dans l'exemple ci-dessus PID du programme 11568, ces informations sont suffisantes pour générer un vidage de thread. Pour générer le vidage, nous utiliserons l'utilitaire
jstack , qui fait partie du JDK, à partir de la version 7. Pour obtenir le vidage, nous
passerons le PID de notre programme à
jstack et spécifierons le drapeau -l (créant une longue liste). La sortie de l'utilitaire sera redirigée vers un fichier texte, c'est-à-dire thread_dump.txt:
jstack -l 11568 > thread_dump.txt
Le fichier thread_dump.txt résultant contient le vidage de thread de notre programme bloqué et contient des informations importantes pour diagnostiquer les causes de blocage.
Si le JDK est utilisé jusqu'à la version 7, pour générer un vidage, vous pouvez utiliser l'utilitaire Linux -
kill avec l'indicateur -3. L'appel de kill -3 enverra au programme un signal SIGQUIT.
Dans notre cas, l'appel sera comme ceci:
kill -3 11568
Analyse simple de vidage de thread
En ouvrant le fichier thread_dump.txt, nous verrons quelque chose comme ceci:
2018-06-19 16:44:44
Sauvegarde complète du thread Java VM HotSpot (TM) 64 bits Server (10.0.1 + 10 mode mixte):
Informations sur la classe de threads SMR:
_java_thread_list = 0x00000250e5488a00, longueur = 13, elements = {
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 en attente à la condition [0x000000b82a9ff000]
java.lang.Thread.State: RUNNABLE
à java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
à java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
à java.lang.ref.Reference.access 000 $ (java.base@10.0.1/Reference.java: 44)
à java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
Synchroniseurs propriétaires verrouillés:
- Aucun
"Finalizer" # 3 daemon prio = 8 os_prio = 1 tid = 0x00000250e4982800 nid = 0x2a54 dans Object.wait () [0x000000b82aaff000]
java.lang.Thread.State: WAITING (sur le moniteur d'objet)
à java.lang.Object.wait (java.base@10.0.1/Native Method)
- en attente sur <0x0000000089509410> (un java.lang.ref.ReferenceQueue $ Lock)
à java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
- en attente de re-verrouillage en attente () <0x0000000089509410> (un java.lang.ref.ReferenceQueue $ Lock)
à java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 172)
à java.lang.ref.Finalizer $ FinalizerThread.run (java.base@10.0.1/Finalizer.java: 216)
Synchroniseurs propriétaires verrouillés:
- Aucun
"Signal Dispatcher" # 4 démon prio = 9 os_prio = 2 tid = 0x00000250e52f2800 nid = 0x2184 exécutable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Synchroniseurs propriétaires verrouillés:
- Aucun
"Attach Listener" # 5 daemon prio = 5 os_prio = 2 tid = 0x00000250e4992800 nid = 0x1624 en attente à la condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Synchroniseurs propriétaires verrouillés:
- Aucun
"C2 CompilerThread0" # 6 démon prio = 9 os_prio = 2 tid = 0x00000250e4995800 nid = 0x4198 en attente à la condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Aucune tâche de compilation
Synchroniseurs propriétaires verrouillés:
- Aucun
"C2 CompilerThread1" # 7 démon prio = 9 os_prio = 2 tid = 0x00000250e49a5800 nid = 0x3b98 en attente à la condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Aucune tâche de compilation
Synchroniseurs propriétaires verrouillés:
- Aucun
"C1 CompilerThread2" # 8 démon prio = 9 os_prio = 2 tid = 0x00000250e49ae800 nid = 0x1a84 en attente à la condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Aucune tâche de compilation
Synchroniseurs propriétaires verrouillés:
- Aucun
"Thread Sweeper" # 9 daemon prio = 9 os_prio = 2 tid = 0x00000250e5324000 nid = 0x5f0 exécutable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Synchroniseurs propriétaires verrouillés:
- Aucun
"Service Thread" # 10 daemon prio = 9 os_prio = 0 tid = 0x00000250e54cd800 nid = 0x169c exécutable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Synchroniseurs propriétaires verrouillés:
- Aucun
Démon "Common-Cleaner" # 11 prio = 8 os_prio = 1 tid = 0x00000250e54cf000 nid = 0x1610 dans Object.wait () [0x000000b82b2fe000]
java.lang.Thread.State: TIMED_WAITING (sur le moniteur d'objet)
à java.lang.Object.wait (java.base@10.0.1/Native Method)
- en attente sur <0x000000008943e600> (un java.lang.ref.ReferenceQueue $ Lock)
à java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
- en attente de re-verrouillage dans wait () <0x000000008943e600> (un java.lang.ref.ReferenceQueue $ Lock)
à jdk.internal.ref.CleanerImpl.run (java.base@10.0.1/CleanerImpl.java: 148)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
à jdk.internal.misc.InnocuousThread.run (java.base@10.0.1/InnocuousThread.java: 134)
Synchroniseurs propriétaires verrouillés:
- Aucun
"Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec en attente d'entrée du moniteur [0x000000b82b4ff000]
java.lang.Thread.State: BLOCKED (sur le moniteur d'objet)
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- en attente de verrouillage <0x00000000894465b0> (un java.lang.Object)
- verrouillé <0x00000000894465a0> (un java.lang.Object)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Synchroniseurs propriétaires verrouillés:
- Aucun
"Thread-1" # 13 prio = 5 os_prio = 0 tid = 0x00000250e54d2000 nid = 0x415c en attente d'entrée du moniteur [0x000000b82b5ff000]
java.lang.Thread.State: BLOCKED (sur le moniteur d'objet)
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- en attente de verrouillage <0x00000000894465a0> (un java.lang.Object)
- verrouillé <0x00000000894465b0> (un java.lang.Object)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Synchroniseurs propriétaires verrouillés:
- Aucun
"DestroyJavaVM" # 14 prio = 5 os_prio = 0 tid = 0x00000250e54d0800 nid = 0x2b8c en attente à la condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Synchroniseurs propriétaires verrouillés:
- Aucun
"VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 exécutable
"GC Thread # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c exécutable
"GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 exécutable
"GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 exécutable
"GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 exécutable
"G1 Main Marker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 exécutable
"G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 exécutable
"G1 Affiner # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c exécutable
"G1 Affiner # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 exécutable
"G1 Affiner # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 exécutable
"G1 Affiner # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 exécutable
"G1 Young RemSet Sampling" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 exécutable
"Thread de tâche périodique VM" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 en attente de condition
Références mondiales JNI: 2
Trouvé un blocage au niveau Java:
===============================
"Thread-0":
en attente de verrouillage du moniteur 0x00000250e4982480 (objet 0x00000000894465b0, un java.lang.Object),
qui est détenu par "Thread-1"
"Thread-1":
en attente de verrouillage du moniteur 0x00000250e4982380 (objet 0x00000000894465a0, un java.lang.Object),
qui est détenu par "Thread-0"
Informations sur la pile Java pour les threads répertoriés ci-dessus:
=================================================== =
"Thread-0":
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- en attente de verrouillage <0x00000000894465b0> (un java.lang.Object)
- verrouillé <0x00000000894465a0> (un java.lang.Object)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
"Thread-1":
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- en attente de verrouillage <0x00000000894465a0> (un java.lang.Object)
- verrouillé <0x00000000894465b0> (un java.lang.Object)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Trouvé 1 blocage.
Informations introductives
Bien qu'à première vue ce fichier puisse sembler trop compliqué et déroutant, en réalité il est assez simple si vous le démontez par étapes étape par étape.
La première ligne indique l'heure à laquelle le vidage a été formé, la seconde - des informations de diagnostic sur la machine virtuelle Java, sur lesquelles le vidage a été reçu:
2018-06-19 16:44:44 Full thread dump Java HotSpot(TM) 64-Bit Server VM (10.0.1+10 mixed mode):
Il n'y a aucune information de flux dans cette section. Ici, le contexte général du système dans lequel le vidage a été collecté est défini.
Informations générales sur le flux
La section suivante fournit des informations sur les unités d'exécution qui étaient en cours d'exécution dans le système au moment de la collecte de vidage:
Informations sur la classe de threads SMR:
_java_thread_list = 0x00000250e5488a00, longueur = 13, elements = {
0x00000250e4979000, 0x00000250e4982800, 0x00000250e52f2800, 0x00000250e4992800,
0x00000250e4995800, 0x00000250e49a5800, 0x00000250e49ae800, 0x00000250e5324000,
0x00000250e54cd800, 0x00000250e54cf000, 0x00000250e54d1800, 0x00000250e54d2000,
0x00000250e54d0800
}
La section suivante répertorie:
Informations sur la récupération de mémoire sécurisée (SMR)Il contient des informations sur les threads en dehors de la JVM, c'est-à-dire ce ne sont pas des threads de machine virtuelle ou des threads de récupération de place. Si vous regardez les adresses de ces threads, vous remarquerez qu'elles correspondent à la valeur de
tid - l'adresse «naturelle, de fer» (native) dans le système d'exploitation, et non l'ID de thread.
Les points de suspension sont utilisés pour masquer les informations redondantes:
"Gestionnaire de référence" # 2 ... tid = 0x00000250e4979000 ...
"Finaliseur" # 3 ... tid = 0x00000250e4982800 ...
"Signal Dispatcher" # 4 ... tid = 0x00000250e52f2800 ...
"Attach Listener" # 5 ... tid = 0x00000250e4992800 ...
"C2 CompilerThread0" # 6 ... tid = 0x00000250e4995800 ...
"C2 CompilerThread1" # 7 ... tid = 0x00000250e49a5800 ...
"C1 CompilerThread2" # 8 ... tid = 0x00000250e49ae800 ...
"Fil de balayage" # 9 ... tid = 0x00000250e5324000 ...
"Fil de service" # 10 ... tid = 0x00000250e54cd800 ...
"Common-Cleaner" # 11 ... tid = 0x00000250e54cf000 ...
"Thread-0" # 12 ... tid = 0x00000250e54d1800 ...
"Thread-1" # 13 ... tid = 0x00000250e54d2000 ...
"DestroyJavaVM" # 14 ... tid = 0x00000250e54d0800 ...
Streams
Juste après le bloc SMR se trouve une liste de threads. Le premier fil de notre liste est le gestionnaire de référence:
"Reference Handler" # 2 daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 en attente à la condition [0x000000b82a9ff000]
java.lang.Thread.State: RUNNABLE
à java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
à java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
à java.lang.ref.Reference.access 000 $ (java.base@10.0.1/Reference.java: 44)
à java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
Synchroniseurs propriétaires verrouillés:
- Aucun
Brève description
La première ligne de chaque thread fournit une description générale. La description contient les éléments suivants:
Section | Exemple | La description |
---|
Nom | "Gestionnaire de référence" | Nom de flux lisible par l'homme. Le nom peut être spécifié en appelant la méthode setName de l'objet Thread . Et passez un appel à getName |
Identifiant | # 2 | Un ID unique attribué à chaque objet de la classe Thread . L'ID est généré pour les threads du système. La valeur initiale est 1. Chaque thread nouvellement créé se voit attribuer son propre ID, précédemment augmenté de 1. Cette propriété de thread en lecture seule peut être obtenue à l'aide de la fonction getId d' un objet de la classe Thread . |
Statut du démon | démon | Le drapeau est un signe que le fil est un démon. S'il s'agit d'un démon, alors le drapeau sera mis. Par exemple, le thread -0 n'est pas un démon. |
Priorité | prio = 10 | La priorité numérique du flux Java. Notez que cette priorité ne correspond pas nécessairement à la priorité du thread associé dans le système d'exploitation. Pour définir la priorité, vous pouvez utiliser la méthode setPriority d' un objet de classe Thread , et pour obtenir Méthode getPriority . |
Priorité du thread OS | os_prio = 2 | Thread prioritaire dans le système d'exploitation. Cette priorité peut différer de celle attribuée au thread Java lié. |
Adresse | tid = 0x00000250e4979000 | L'adresse du flux Java. Cette adresse est un pointeur vers l'objet natif Java Native Interface (JNI) de la classe Thread (un objet C ++ Thread qui est connecté au thread Java via JNI). Cette valeur est obtenue en plaçant un pointeur sur ce (l'objet C ++ associé à ce thread Java) en entier. Voir ligne 879 dans hotspot / share / runtime / thread.cpp :
st-> print ("tid =" INTPTR_FORMAT "", p2i (this));
Bien que la clé de cet objet ( tid ) puisse ressembler à un ID de flux, en fait, c'est l'adresse de l'objet connecté JNI C ++ Thread , et ce n'est pas la valeur qui renvoie la méthode getId du thread Java. |
ID de thread du système d'exploitation | nid = 0x3c28 | Identificateur unique du thread du système d'exploitation auquel le thread Java est lié. Cette valeur est sortie avec le code suivant: ligne 42 dans hotspot / share / runtime / osThread.cpp :
st-> print ("nid = 0x% x", thread_id ());
|
Statut | attente sous condition | Statut lisible par l'homme du thread actuel. Cette ligne affiche des informations supplémentaires sur le statut simple du flux (voir ci-dessous), qui peuvent être utilisé pour comprendre ce que le thread allait faire (c'est-à-dire si le thread essayait d'obtenir un verrou ou en attendant que la condition de déverrouillage soit remplie). |
Dernier pointeur de pile Java connu | [0x000000b82a9ff000] | Le dernier pointeur de pile (SP) connu associé à ce flux. Cette valeur est obtenue en utilisant du code C ++ natif mélangé avec du code Java utilisant JNI. La valeur est retournée par la fonction last_Java_sp () , ligne 2886 dans hotspot / share / runtime / thread.cpp :
st-> print_cr ("[" INTPTR_FORMAT "]",
(intptr_t) last_Java_sp () & ~ right_n_bits (12));
Pour les vidages de threads simples, ces informations sont presque inutiles. Cependant, dans des cas complexes, SP peut être utilisé pour suivre les serrures. |
Statut du flux
La deuxième ligne est l'état actuel du flux. Les états de flux possibles sont répertoriés dans l'énumération:
Thread.State :
NOUVEAU
RUNNABLE
BLOQUÉ
EN ATTENTE
TIMED_WAITING
RÉSILIÉ
Voir la
documentation pour plus de détails.
Trace de pile de threads
La section suivante contient la trace de pile du flux au moment où le vidage a été effectué. Cette trace de pile est très similaire à une trace de pile, qui est levée par une exception non interceptée. Et il contient les noms des classes et des chaînes qui ont été exécutées au moment de la formation du vidage. Dans le cas du flux du gestionnaire de référence, nous ne voyons rien d'intéressant.
Cependant, il y a quelque chose d'intéressant dans la trace de thread Thread-02 qui est différent de la trace standard:
"Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec en attente d'entrée du moniteur [0x000000b82b4ff000]
java.lang.Thread.State: BLOCKED (sur le moniteur d'objet)
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- en attente de verrouillage <0x00000000894465b0> (un java.lang.Object)
- verrouillé <0x00000000894465a0> (un java.lang.Object)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Synchroniseurs propriétaires verrouillés:
- Aucun
Dans la trace, nous voyons que des informations sur le verrou ont été ajoutées. Ce thread attend un verrou sur l'objet avec l'adresse 0x00000000894465b0 (type d'objet java.lang.Object). De plus, le thread lui-même détient le verrou avec l'adresse 0x00000000894465a0 (également un java.lang.Object). Ces informations nous seront utiles ultérieurement pour le diagnostic de blocage.
Primitives de synchronisation capturées (synchroniseur propriétaire)
La dernière section répertorie les primitives de synchronisation capturées par le flux. Ce sont des objets qui peuvent être utilisés pour synchroniser des threads, par exemple des verrous.
Selon la documentation Java officielle,
Ownable Synchronizer est le descendant de
AbstractOwnableSynchronizer (ou sa sous-classe), qui peut être exclusivement capturé par le flux à des fins de synchronisation.
ReentrantLock et
write-lock , mais pas le
read-lock de la classe
ReentrantReadWriteLock sont deux bons exemples de tels «synchroniseurs propriétaires» offerts par la plate-forme.
Pour plus d'informations à ce sujet, vous pouvez vous référer à ce
poster .
Threads JVM
La section suivante du vidage contient des informations sur les threads techniques JVM qui ne font pas partie de l'application et sont associés aux threads du système d'exploitation. Parce que ces flux fonctionnent en dehors de l'application, ils n'ont pas d'identifiants de flux. Le plus souvent, ce sont des threads de garbage collector et d'autres threads techniques JVM:
"VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 exécutable
"GC Thread # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c exécutable
"GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 exécutable
"GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 exécutable
"GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 exécutable
"G1 Main Marker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 exécutable
"G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 exécutable
"G1 Affiner # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c exécutable
"G1 Affiner # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 exécutable
"G1 Affiner # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 exécutable
"G1 Affiner # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 exécutable
"G1 Young RemSet Sampling" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 exécutable
"Thread de tâche périodique VM" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 en attente de condition
Liens mondiaux JNI
Cette section indique le nombre de références globales utilisées par la JVM via JNI. Ces liens ne sont pas desservis par le garbage collector et peuvent provoquer une fuite de mémoire dans certaines circonstances.
Références mondiales JNI: 2
Dans la plupart des cas simples, ces informations ne sont pas utilisées. Cependant, l'importance des références mondiales doit être comprise. Voir cet
article pour plus de détails.
Fils bloqués
La dernière section contient des informations sur les blocages trouvés.
Si ceux-ci ne sont pas trouvés, la section sera vide. Parce que Nous avons spécifiquement développé une application avec des verrous, dans notre cas cette section est. Un verrou a été détecté lors du vidage et affiche le message suivant:
Trouvé un blocage au niveau Java:
===============================
"Thread-0":
en attente de verrouillage du moniteur 0x00000250e4982480 (objet 0x00000000894465b0, un java.lang.Object),
qui est détenu par "Thread-1"
"Thread-1":
en attente de verrouillage du moniteur 0x00000250e4982380 (objet 0x00000000894465a0, un java.lang.Object),
qui est détenu par "Thread-0"
Informations sur la pile Java pour les threads répertoriés ci-dessus:
=================================================== =
"Thread-0":
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- en attente de verrouillage <0x00000000894465b0> (un java.lang.Object)
- verrouillé <0x00000000894465a0> (un java.lang.Object)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
"Thread-1":
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
- en attente de verrouillage <0x00000000894465a0> (un java.lang.Object)
- verrouillé <0x00000000894465b0> (un java.lang.Object)
à java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
Trouvé 1 blocage.
La première sous-section décrit le scénario de blocage:
Thread-0 s'attend à pouvoir capturer le moniteur (c'est un accès au bloc
synchronisé (secondResource) dans notre application), en même temps ce thread tient un moniteur qui essaie de capturer le Thread-1 (c'est accéder au même fragment de code:
synchronized (secondResource ) dans notre application).
Cette serrure circulaire est autrement appelée
impasse . Dans l'image ci-dessous
cette situation est présentée sous forme graphique:

Dans la deuxième sous-section, la trace de pile est donnée pour les deux threads bloqués.
Cette trace de pile nous permet de suivre le fonctionnement de chaque thread jusqu'à ce qu'un verrouillage se produise.
Dans notre cas, si nous regardons la ligne:
à DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34) , alors nous verrons la partie problématique du code:
printLockedResource (secondResource);
Cette ligne est la première ligne du bloc synchronisé, qui est la raison du verrouillage, et nous indique que la synchronisation sur la seconde ressource est la raison du verrouillage mutuel. Pour remédier à la situation, nous devons nous assurer que les deux threads ont le même ordre de synchronisation sur les ressources resourceA et resourceB. Si nous le faisons, nous arriverons à l'application suivante:
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); } } }
Cette application se terminera sans verrouillage, et par conséquent, nous obtiendrons la sortie suivante (notez que les adresses de la classe Object ont changé):
Thread-0: ressource verrouillée -> java.lang.Object@1ad895d1
Thread-0: ressource verrouillée -> java.lang.Object@6e41d7dd
Thread-1: ressource verrouillée -> java.lang.Object@1ad895d1
Thread-1: ressource verrouillée -> java.lang.Object@6e41d7dd
, , thread dump, . ( deadlock-). , .
Thread Dump-
.
JVM . ( , ).
.
- — Thread Dump Analyzers (TDAs). Java thread dump- - , . , . , .
TDA:
. .
Conclusion
Thread dump- — Java-, . , .
deadlock, . . , — .
, Java- thread dump-. , .
, thread dump — « » , , Java-.
Java . , deadlock- ., .