Parlons du thread dump et de la manière de l’analyser.
Nous verrons également comment il permet de localiser les problèmes et quels sont les analyseurs que vous pouvez utiliser.
Qu’est-ce qu’un thread ?
Un processus est un programme informatique chargé dans la mémoire de l’ordinateur et en cours d’exécution. Il peut être exécuté par un processeur ou un ensemble de processeurs. Un processus est décrit dans la mémoire avec des informations importantes telles que les variables, les fichiers, le compteur du programme, les registres, les signaux, etc.
Un processus peut être composé de plusieurs processus légers appelés ” threads”. Cela permet d’obtenir un parallélisme dans lequel un processus est divisé en plusieurs threads. Il en résulte de meilleures performances. Tous les threads d’un processus partagent le même espace mémoire et dépendent les uns des autres.
Fiches de threads
Lorsque le processus est en cours d’exécution, nous pouvons détecter l’état actuel de l’exécution des threads dans le processus à l’aide de threads dumps. Un thread dump contient un instantané de tous les threads actifs à un moment donné de l’exécution d’un programme. Il contient toutes les informations pertinentes sur le thread et son état actuel.
Une application moderne implique aujourd’hui un grand nombre de threads. Chaque thread nécessite certaines ressources et effectue certaines activités liées au processus. Cela peut améliorer les performances d’une application car les threads peuvent utiliser les cœurs disponibles du processeur.
Mais il y a des compromis, par exemple, il arrive que plusieurs threads ne se coordonnent pas bien entre eux et qu’une situation de blocage se produise. Ainsi, si quelque chose ne va pas, nous pouvons utiliser les thread dumps pour inspecter l’état de nos threads.
Le thread dump en Java
Un thread dump de la JVM est une liste de l’état de tous les threads qui font partie du processus à un moment donné. Il contient des informations sur la pile du thread, présentées sous la forme d’une trace de pile. Comme il est écrit en clair, son contenu peut être sauvegardé pour être consulté ultérieurement. L’analyse des thread dumps peut aider à
- Optimiser les performances de la JVM
- Optimiser les performances de l’application
- Diagnostiquer des problèmes, par exemple un blocage, une contention de thread, etc.
Génération de thread dumps
Il existe de nombreuses façons de générer des vidages de threads. Vous trouverez ci-dessous quelques outils basés sur la JVM qui peuvent être exécutés à partir de la ligne de commande/terminal (outils CLI) ou du répertoire /bin (outils GUI) du dossier d’installation de Java.
Explorons-les.
#1. jStack
La manière la plus simple de générer un thread dump est d’utiliser jStack. jStack est fourni avec la JVM et peut être utilisé à partir de la ligne de commande. Ici, nous avons besoin du PID du processus pour lequel nous voulons générer le thread dump. Pour obtenir le PID, nous pouvons utiliser la commande jps comme indiqué ci-dessous.
jps -l
jps
répertorie tous les identifiants des processus Java.
Sous Windows
C:\NProgram Files\NJava\Njdk1.8.0_171\Nbin>jps -l
47172 portal
6120 sun.tools.jps.Jps
C:\Program Files\Java\jdk1.8.0_171\bin>
Sous Linux
[geekfkare@localhost ~]# jps -l
1088 /opt/keycloak/jboss-modules.jar
26680 /var/lib/jenkins/workspace/kyc/kyc/target/kyc-1.0.jar
7193 jdk.jcmd/sun.tools.jps.Jps
2058 /usr/share/jenkins/jenkins.war
11933 /var/lib/jenkins/workspace/admin-portal/target/portal-1.0.jar
[geekfkare@localhost ~]#
Comme nous pouvons le voir ici, nous obtenons une liste de tous les processus Java en cours d’exécution. Elle contient l’identifiant de la VM locale pour le processus Java en cours d’exécution et le nom de l’application dans les colonnes un et deux respectivement. Maintenant, pour générer le thread dump, nous utilisons le programme jStack avec le drapeau -l qui crée une longue liste de sortie du dump. Nous pouvons également diriger la sortie vers un fichier texte de notre choix.
jstack -l 26680<br>
[geekfkare@localhost ~]# jstack -l 26680
2020-06-27 06:04:53
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode) :
"Attach Listener" #16287 daemon prio=9 os_prio=0 tid=0x00007f0814001800 nid=0x4ff2 waiting on condition [0x000000000000]
java.lang.Thread.State : RUNNABLE
Synchroniseurs verrouillés propres :
- Aucun
"logback-8" #2316 daemon prio=5 os_prio=0 tid=0x00007f07e0033000 nid=0x4792 waiting on condition [0x00007f07baff8000]
java.lang.Thread.State : WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking pour attendre <0x00000006ca9a1fc0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueueue.take(ScheduledThreadPoolExecutor.java:1081)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Synchroniseurs verrouillés propres :
- Aucun
"logback-7" #2315 daemon prio=5 os_prio=0 tid=0x00007f07e0251800 nid=0x4791 waiting on condition [0x00007f07bb0f9000]
java.lang.Thread.State : WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking pour attendre <0x00000006ca9a1fc0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueueue.take(ScheduledThreadPoolExecutor.java:1081)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Synchroniseurs verrouillés propres :
- Aucun
#2. jvisualvm
Jvisualvm est un outil GUI qui nous aide à dépanner, surveiller et profiler les applications Java. Il est également fourni avec la JVM et peut être lancé à partir du répertoire /bin de notre installation Java. Il est très intuitif et facile à utiliser. Parmi d’autres options, il nous permet également de capturer le thread dump d’un processus particulier.
Pour visualiser le thread dump d’un processus particulier, nous pouvons faire un clic droit sur le programme et sélectionner Thread Dump dans le menu contextuel.
#3. jcmd
JCMD est un utilitaire de ligne de commande fourni avec le JDK et utilisé pour envoyer des requêtes de commande de diagnostic à la JVM.
Il ne fonctionne toutefois que sur la machine locale où l’application Java est exécutée. Il peut être utilisé pour contrôler les enregistrements Java Flight, diagnostiquer et dépanner la JVM et les applications Java. Nous pouvons utiliser la commande Thread.print
de jcmd pour obtenir une liste de thread dumps pour un processus particulier spécifié par le PID.
Vous trouverez ci-dessous un exemple d’utilisation de jcmd
.
jcmd 28036 Thread.print
C:\NProgram Files\NJava\Njdk1.8.0_171\Nbin>jcmd 28036 Thread.print
28036 :
2020-06-27 21:20:02
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode) :
"Bundle File Closer" #14 daemon prio=5 os_prio=0 tid=0x0000000021d1c000 nid=0x1d4c in Object.wait() [0x0000244ef000]
java.lang.Thread.State : WAITING (sur le moniteur de l'objet)
at java.lang.Object.wait(Méthode native)
at java.lang.Object.wait(Source inconnue)
at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.getNextEvent(EventManager.java:403)
- bloqué <0x000000076f380a88> (a org.eclipse.osgi.framework.eventmgr.EventManager$EventThread)
at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:339)
"Active Thread : Equinox Container : 0b6cc851-96cd-46de-a92b-253c7f7671b9" #12 prio=5 os_prio=0 tid=0x0000000022e61800 nid=0xbff4 waiting on condition [0x00000000243ee000]
java.lang.Thread.State : TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking pour attendre <0x000000076f388188> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueueue.take(Unknown Source)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Source inconnue)
at java.util.concurrent.ThreadPoolExecutor.getTask(Source inconnue)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Source inconnue)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Source inconnue)
at java.lang.Thread.run(Source inconnue)
"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000000021a7b000 nid=0x2184 runnable [0x000000000000]
java.lang.Thread.State : RUNNABLE
"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x00000000219f5000 nid=0x1300 waiting on condition [0x000000000000]
java.lang.Thread.State : RUNNABLE
"C2 CompilerThread2 #8 daemon prio=9 os_prio=2 tid=0x00000000219e0000 nid=0x48f4 waiting on condition [0x0000000000000000]
java.lang.Thread.State : RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x00000000219df000 nid=0xb314 waiting on condition [0x000000000000]
java.lang.Thread.State : RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x00000000219db800 nid=0x2260 waiting on condition [0x000000000000]
java.lang.Thread.State : RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x00000000219d9000 nid=0x125c waiting on condition [0x0000000000000000]
java.lang.Thread.State : RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x00000000219d8000 nid=0x834 runnable [0x000000000000]
java.lang.Thread.State : RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001faf3000 nid=0x36c0 in Object.wait() [0x0000000021eae000]
java.lang.Thread.State : WAITING (sur le moniteur de l'objet)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076f390180> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(Unknown Source)
- locked <0x000000076f390180> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(Source inconnue)
at java.lang.ref.Finalizer$FinalizerThread.run(Source inconnue)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000005806000 nid=0x13c0 in Object.wait() [0x0000219af000]
java.lang.Thread.State : WAITING (sur le moniteur de l'objet)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076f398178> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Source inconnue)
at java.lang.ref.Reference.tryHandlePending(Source inconnue)
- locked <0x000000076f398178> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Source inconnue)
"main" #1 prio=5 os_prio=0 tid=0x000000000570e800 nid=0xbf8 runnable [0x0000000000fec000]
java.lang.Thread.State : RUNNABLE
at java.util.zip.ZipFile.open(Méthode native)
at java.util.zip.ZipFile.<init>(Source inconnue)
at java.util.zip.ZipFile.<init>(Source inconnue)
at java.util.zip.ZipFile.<init>(Source inconnue)
at org.eclipse.osgi.framework.util.SecureAction.getZipFile(SecureAction.java:307)
at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.getZipFile(ZipBundleFile.java:136)
at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.lockOpen(ZipBundleFile.java:83)
at org.eclipse.osgi.storage.bundlefile.ZipBundleFile.getEntry(ZipBundleFile.java:290)
at org.eclipse.equinox.weaving.hooks.WeavingBundleFile.getEntry(WeavingBundleFile.java:65)
at org.eclipse.osgi.storage.bundlefile.BundleFileWrapper.getEntry(BundleFileWrapper.java:55)
at org.eclipse.osgi.storage.BundleInfo$Generation.getRawHeaders(BundleInfo.java:130)
- locked <0x000000076f85e348> (a java.lang.Object)
at org.eclipse.osgi.storage.BundleInfo$CachedManifest.get(BundleInfo.java:599)
at org.eclipse.osgi.storage.BundleInfo$CachedManifest.get(BundleInfo.java:1)
at org.eclipse.equinox.weaving.hooks.SupplementerRegistry.addSupplementer(SupplementerRegistry.java:172)
at org.eclipse.equinox.weaving.hooks.WeavingHook.initialize(WeavingHook.java:138)
at org.eclipse.equinox.weaving.hooks.WeavingHook.start(WeavingHook.java:208)
at org.eclipse.osgi.storage.FrameworkExtensionInstaller.startActivator(FrameworkExtensionInstaller.java:261)
at org.eclipse.osgi.storage.FrameworkExtensionInstaller.startExtensionActivators(FrameworkExtensionInstaller.java:198)
at org.eclipse.osgi.internal.framework.SystemBundleActivator.start(SystemBundleActivator.java:112)
at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:815)
at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:1)
at java.security.AccessController.doPrivileged(Méthode native)
at org.eclipse.osgi.internal.framework.BundleContextImpl.startActivator(BundleContextImpl.java:808)
at org.eclipse.osgi.internal.framework.BundleContextImpl.start(BundleContextImpl.java:765)
at org.eclipse.osgi.internal.framework.EquinoxBundle.startWorker0(EquinoxBundle.java:1005)
at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle$EquinoxSystemModule.initWorker(EquinoxBundle.java:190)
at org.eclipse.osgi.container.SystemModule.init(SystemModule.java:99)
at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle.init(EquinoxBundle.java:272)
at org.eclipse.osgi.internal.framework.EquinoxBundle$SystemBundle.init(EquinoxBundle.java:257)
at org.eclipse.osgi.launch.Equinox.init(Equinox.java:171)
at org.eclipse.core.runtime.adaptor.EclipseStarter.startup(EclipseStarter.java:316)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:251)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Méthode native)
at sun.reflect.NativeMethodAccessorImpl.invoke(Source inconnue)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Source inconnue)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:661)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:597)
at org.eclipse.equinox.launcher.Main.run(Main.java:1476)
"VM Thread" os_prio=2 tid=0x000000001fae8800 nid=0x32cc runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000005727800 nid=0x3264 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000005729000 nid=0xbdf4 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000572a800 nid=0xae6c runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000572d000 nid=0x588 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000572f000 nid=0xac0 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000005730800 nid=0x380 exécutable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000005733800 nid=0x216c exécutable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000005734800 nid=0xb930 exécutable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000021a8d000 nid=0x2dcc waiting on condition
Références globales JNI : 14
C:\Program Files\Java\jdk1.8.0_171\bin>
#4. JMC
JMC est l’acronyme de Java Mission Control. Il s’agit d’un outil GUI open-source fourni avec le JDK et utilisé pour collecter et analyser les données de l’application Java.
Il peut être lancé à partir du dossier /bin de notre installation Java. Les administrateurs et les développeurs Java utilisent cet outil pour recueillir des informations détaillées de bas niveau sur le comportement de la JVM et de l’application. Il permet une analyse détaillée et efficace des données collectées par Java Flight Recorder.
En lançant jmc
, nous pouvons voir la liste des processus Java en cours d’exécution sur la machine locale. Une connexion à distance est également possible. Sur un processus particulier, nous pouvons faire un clic droit et choisir Start Flight Recording, puis vérifier les vidages de threads dans l’onglet Threads.
#5. jconsole
jconsole est un outil d’extension de gestion Java utilisé pour la gestion et la surveillance des plaintes.
Il dispose également d’un ensemble d’opérations prédéfinies sur l’agent JMX que l’utilisateur peut effectuer. Il permet à l’utilisateur de détecter et d’analyser la trace de pile d’un programme en cours. Il peut être lancé à partir du dossier /bin de notre installation Java.
En utilisant l’outil GUI jconsole, nous pouvons inspecter la trace de pile de chaque thread lorsque nous le connectons à un processus Java en cours d’exécution. Ensuite, dans l’onglet Thread, nous pouvons voir le nom de tous les threads en cours d’exécution. Pour détecter un blocage, nous pouvons cliquer sur le bouton Détecter le blocage en bas à droite de la fenêtre. Si un blocage est détecté, il apparaîtra dans un nouvel onglet ; dans le cas contraire, le message No Deadlock Detected (Aucun blocage détecté ) s’affichera.
#6. ThreadMxBean
ThreadMXBean est l’interface de gestion du système de threads de la machine virtuelle Java appartenant au paquetage java.lang.Management. Il est principalement utilisé pour détecter les threads qui sont entrés dans une situation de blocage et obtenir des détails à leur sujet.
Nous pouvons utiliser l’interface ThreadMxBean pour capturer de manière programmatique le thread dump. La méthode getThreadMXBean()
de ManagementFactory
est utilisée pour obtenir une instance de l’interface ThreadMXBean
. Elle renvoie le nombre de threads actifs démon et non démon. ManagementFactory est une classe d’usine permettant d’obtenir des beans gérés pour la plateforme Java.
private static String getThreadDump (boolean lockMonitors, boolean lockSynchronizers) {
StringBuffer threadDump = new StringBuffer (System.lineSeparator ()) ;
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean () ;
for (ThreadInfo threadInfo : threadMXBean.dumpAllThreads (lockMonitors, lockSynchronizers)) {
threadDump.append (threadInfo.toString ()) ;
}
return threadDump.toString () ;
}
Analyse manuelle des vidages de threads
L’analyse des vidages de threads peut s’avérer très utile pour identifier les problèmes dans les processus multithreads. Les problèmes tels que les blocages, la contention des verrous et l’utilisation excessive de l’unité centrale par des vidages de threads individuels peuvent être résolus en visualisant les états des vidages de threads individuels.
Le débit maximal de l’application peut être atteint en rectifiant l’état de chaque thread après avoir analysé le thread dump.
Par exemple, si un processus utilise beaucoup de CPU, nous pouvons déterminer si un thread utilise le plus de CPU. Si c’est le cas, nous convertissons son numéro LWP en un nombre hexadécimal. Ensuite, à partir du thread dump, nous pouvons trouver le thread dont le nid est égal au nombre hexadécimal obtenu précédemment. En utilisant la trace de la pile de ce thread, nous pouvons localiser le problème. Découvrons l’identifiant du processus de la discussion à l’aide de la commande ci-dessous.
ps -mo pid,lwp,stime,time,cpu -C java
[geekfkare@localhost ~]# ps -mo pid,lwp,stime,time,cpu -C java
PID LWP STIME TIME %CPU
26680 - Dec07 00:02:02 99.5
- 10039 Déc07 00:00:00 0.1
- 10040 Déc07 00:00:00 95,5
Jetons un coup d’oeil au morceau de thread dump ci-dessous. Pour obtenir le thread dump du processus 26680, utilisez jstack -l 26680
[geekfkare@localhost ~]# jstack -l 26680
2020-06-27 09:01:29
<strong>Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode) :</strong>
"Attach Listener" #16287 daemon prio=9 os_prio=0 tid=0x00007f0814001800 nid=0x4ff2 waiting on condition [0x000000000000]
java.lang.Thread.State : RUNNABLE
Synchroniseurs verrouillés propres :
- Aucun
.
.
.
.
.
.
.
"<strong>Reference Handler</strong>" #2 daemon prio=10 os_prio=0 tid=0x00007f085814a000 nid=0x6840 in Object.wait() [0x00007f083b2f1000]
java.lang.Thread.State : WAITING (sur le moniteur de l'objet)
at java.lang.Object.wait(Méthode native)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000006c790fbd0> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Synchroniseurs verrouillés propres :
- Aucun
"VM Thread" os_prio=0 tid=0x00007f0858140800 nid=0x683f runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f0858021000 nid=0x683b runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f0858022800 nid=0x683c runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f0858024800 nid=0x683d runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f0858026000 nid=0x683e exécutable
"VM Periodic Task Thread" os_prio=0 tid=0x00007f08581a0000 nid=0x6847 waiting on condition
Références globales JNI : 1553
Voyons maintenant quelles sont les choses que nous pouvons explorer en utilisant les thread dumps. Si nous observons le thread dump, nous pouvons voir beaucoup de contenu, ce qui peut être accablant. Cependant, si nous prenons une étape à la fois, cela peut être assez simple à comprendre. Comprenons la première ligne
2020-06-27 09:01:29
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.221-b11 mixed mode) :
La ligne ci-dessus affiche l’heure à laquelle le dump a été généré, ainsi que des informations sur la JVM utilisée. Ensuite, à la fin, nous pouvons voir la liste des threads, le premier d’entre eux étant notre thread ReferenceHandler.
Analyse des threads bloqués
Si nous analysons les journaux de vidage de threads ci-dessous, nous pouvons constater qu’ils ont détecté des threads avec le statut BLOCKED, ce qui rend les performances d’une application très lentes. Donc, si nous pouvons trouver les threads BLOCKED, nous pouvons essayer d’extraire les threads liés aux verrous que les threads essaient d’obtenir. L’analyse de la trace de pile du thread qui détient actuellement le verrou peut aider à résoudre le problème.
[geekfkare@localhost ~]# jstack -l 26680
.
.
.
.
" DB-Processor-13" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f000]
java.lang.Thread.State : <strong>BLOCKED</strong> (sur le moniteur d'objet)
at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
- waiting to lock <0xe0375410> (a beans.ConnectionPool)
at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
"DB-Processor-14" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f020]
java.lang.Thread.State : <strong>BLOCKED</strong> (sur le moniteur d'objet)
at beans.ConnectionPool.getConnection(ConnectionPool.java:102)
- waiting to lock <0xe0375410> (a beans.ConnectionPool)
at beans.cus.ServiceCnt.getTodayCount(ServiceCnt.java:111)
at beans.cus.ServiceCnt.insertCount(ServiceCnt.java:43)
.
.
.
.
Analyse d’un thread bloqué
Une autre application très courante des thread dumps est la détection des blocages. La détection et la résolution des blocages peuvent être grandement facilitées par l’analyse des vidages de threads.
Un blocage est une situation impliquant au moins deux threads où la ressource requise par un thread pour poursuivre l’exécution est verrouillée par un autre thread et, en même temps, la ressource requise par le second thread est verrouillée par le premier thread.
Ainsi, aucun des threads ne peut poursuivre l’exécution, ce qui entraîne une situation de blocage et finit par bloquer l’application. Si des dreadlocks sont présents, la dernière section du thread dump imprimera les informations concernant le deadlock comme suit.
"Thread-0" :
attente du verrouillage du moniteur 0x00000250e4982480 (objet 0x00000000894465b0, un java.lang.Object),
qui est détenu par "Thread-1"
"Thread-1" :
attente du verrouillage du moniteur 0x00000250e4982380 (objet 0x00000000894465a0, un java.lang.Object),
qui est détenu par "Thread-0"
.
.
.
"Thread-0" :
at DeadlockedProgram$DeadlockedRunnableImplementation.run(DeadlockedProgram.java:34)
- waiting to lock <0x00000000894465b0> (a java.lang.Object)
- locked <0x00000000894465a0> (a java.lang.Object)
at java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
"Thread-1" :
at DeadlockedProgram $DeadlockRunnableImplementation.run(DeadlockedProgram.java:34)
- waiting to lock <0x00000000894465a0> (a java.lang.Object)
- locked <0x00000000894465b0> (a java.lang.Object)
at java.lang.Thread.run(java.base@10.0.1/Thread.java:844)
Ici, nous pouvons voir les informations sur les blocages dans un format relativement lisible par l’homme.
Par ailleurs, si nous résumons tous les éléments ci-dessus, nous obtenons les informations suivantes.
- Reference handler est le nom lisible du thread.
- #2 est l’identifiant unique du thread.
- daemon indique si le thread est un daemon.
- La priorité numérique de la discussion est indiquée par prio=10.
- L’état actuel du thread est indiqué par waiting on condition.
- Nous voyons ensuite la trace de la pile, qui comprend les informations sur le verrouillage.
Analyseurs de traces de threads
Outre l’analyse manuelle, de nombreux outils sont disponibles pour analyser les thread dumps, à la fois en ligne et hors ligne. Vous trouverez ci-dessous une liste d’outils que vous pouvez utiliser en fonction de vos besoins.
Commençons par les outils en ligne.
#1. Fast Thread
Fast Thread est l’outil d’analyse de thread dumps préféré des ingénieurs DevOps pour résoudre des problèmes de production complexes. Il s’agit d’un analyseur de thread dump en ligne. Vous pouvez télécharger le thread dump sous forme de fichier ou le copier-coller directement.
En fonction de la taille, il analysera le thread dump et affichera les informations comme indiqué dans la capture d’écran.
Fonctionnalités
- Dépannez les crashs JVM, les ralentissements, les fuites de mémoire, les gels, les pics de CPU
- RCA instantané (n’attendez pas les fournisseurs)
- Tableau de bord intuitif
- Prise en charge de l’API REST
- Apprentissage automatique
#2. Spotify Thread Dump Analyzer
Le Spotify Thread Dump Analyzer est sous la version 2.0 de la licence Apache. Il s’agit d’un outil en ligne qui accepte le thread dump sous forme de fichier ou qui peut directement copier et coller le thread dump. En fonction de la taille, il analysera le thread dump et affichera les informations comme indiqué dans la capture d’écran.
#3. Jstack review
Jstack.review analyse les thread dumps de Java à partir du navigateur. Cette page ne concerne que le côté client.
#4. Site 24×7
Cet outil est indispensable pour détecter les threads défectueux qui dégradent les performances de la machine virtuelle Java (JVM). Les problèmes tels que les blocages, la contention des verrous et l’utilisation excessive de l’unité centrale par des dumps de threads individuels peuvent être résolus en visualisant les états des dumps de threads individuels.
Le débit maximal de l’application peut être atteint en rectifiant l’état de chaque thread fourni par l’outil.
Maintenant, explorons les outils hors ligne.
Lorsqu’il s’agit de profilage, seul le meilleur outil est suffisant.
#1. JProfiler
JProfiler est l’un des analyseurs de threads les plus populaires parmi les développeurs Java. L’interface intuitive de JProfiler vous aide à résoudre les goulets d’étranglement en matière de performances, à repérer les fuites de mémoire et à comprendre les problèmes de threading.
JProfiler prend en charge le profilage sur les plates-formes suivantes :
- Windows
- macOS
- Linux
- FreeBSD
- Solaris
- AIX
- HP-UX
Vous trouverez ci-dessous quelques caractéristiques qui font de JProfiler le meilleur choix pour le profilage de nos applications sur la JVM.
Caractéristiques
- Prise en charge du profilage des bases de données pour JDBC, JPA et NoSQL
- La prise en charge de l’édition Java Enterprise est également disponible
- Présente des informations de haut niveau sur les appels RMI
- Analyse approfondie des fuites de mémoire
- Capacités étendues d’assurance qualité
- Le profileur de threads intégré est étroitement intégré aux vues de profilage du processeur.
- Prise en charge des plates-formes, des IDE et des serveurs d’application.
#2. IBM TMDA
IBM Thread and Monitor Dump Analyzer for Java(TMDA) est un outil qui permet d’identifier les blocages, les impasses, la contention des ressources et les goulets d’étranglement dans les vidages de threads Java. Il s’agit d’un produit IBM, mais l’outil TMDA est fourni sans aucune garantie ni assistance ; toutefois, IBM s’efforce de corriger et d’améliorer l’outil au fil du temps.
#3. ManageEngine
Le gestionnaire d’applicationsManageEngine peut vous aider à surveiller la mémoire vive et la mémoire non vive de la JVM. Il est même possible de configurer des seuils et d’être alerté par e-mail, SMS, etc. et de s’assurer que l’application Java est bien réglée.
#4. YourKit
YourKit se compose des produits suivants, appelés Kit.
- Java Profiler – Profiler complet à faible coût pour les plates-formes Java EE et Java SE.
- YouMonitor – Surveillance des performances et profilage de Jenkins, TeamCity, Gradle, Maven, Ant, JUnit et TestNG.
- .NET Profiler – Profileur de performance et de mémoire facile à utiliser pour le cadre .NET.
Conclusion
Vous savez maintenant comment les thread dumps sont utiles pour comprendre et diagnostiquer les problèmes dans les applications multithreads. Si vous connaissez bien les thread dumps (leur structure, les informations qu’ils contiennent, etc.), vous pouvez les utiliser pour identifier rapidement les causes des problèmes.