Co je Thread Dump a jak je analyzovat?

Pojďme se zaměřit na téma výpisů vláken a jejich analýzu.

Probereme si, jak nám tyto výpisy pomáhají odhalovat problémy a také se podíváme na některé nástroje, které můžeme k analýze použít.

Co je to vlákno?

Proces je v podstatě běžící počítačový program, který je uložen v paměti. K jeho spuštění se využívá procesor nebo sada procesorů. Proces je v paměti popsán pomocí klíčových informací, mezi které patří například úložiště proměnných, popisovač souborů, programový čítač, registry a signály.

Proces se může skládat z mnoha tzv. lehkých procesů, které označujeme jako vlákna. To umožňuje dosáhnout paralelismu, kdy se proces rozdělí na více vláken, což vede k lepšímu výkonu. Všechna vlákna v rámci jednoho procesu sdílejí stejný paměťový prostor a jsou na sobě navzájem závislá.

Stavy vláken

Během provádění procesu můžeme zjistit aktuální stav vláken pomocí výpisů vláken. Takový výpis zachycuje snímek všech aktivních vláken v určitém okamžiku běhu programu a obsahuje klíčové informace o daném vláknu a jeho aktuálním stavu.

Moderní aplikace často pracují s velkým počtem vláken. Každé z těchto vláken potřebuje určité zdroje a vykonává specifické operace související s procesem. Využití více vláken může vést ke zvýšení výkonu aplikace, protože vlákna mohou využít dostupné procesorové jádra.

Nicméně, existují i potenciální nevýhody. Například, může dojít k situaci, kdy vlákna spolu nebudou dostatečně koordinována a dojde k uváznutí. Pokud se tedy objeví nějaký problém, výpisy vláken nám umožní zkontrolovat stav všech vláken a najít příčinu.

Výpis vláken v Javě

Výpis vláken JVM je záznam stavu všech vláken spojených s procesem v daném okamžiku. Obsahuje informace o zásobníku vlákna, prezentované jako trasování zásobníku. Výpis je uložen jako otevřený text, což umožňuje jeho pozdější analýzu. Analýza výpisů vláken je užitečná pro:

  • Optimalizaci výkonu JVM
  • Zlepšení výkonu aplikace
  • Diagnostiku problémů, jako jsou uváznutí nebo spory o vlákna

Generování výpisů vláken

Existuje několik metod pro generování výpisů vláken. Níže jsou uvedeny některé nástroje, které jsou součástí JVM a je možné je spustit z příkazové řádky/terminálu (CLI nástroje) nebo z adresáře /bin (GUI nástroje) instalační složky Java.

Pojďme si je prozkoumat.

#1. jStack

Nejjednodušší cestou k získání výpisu vlákna je použití nástroje jStack, který je součástí JVM a spouští se z příkazového řádku. Pro jeho použití potřebujeme PID procesu, pro který chceme výpis získat. PID můžeme získat pomocí příkazu jps.

jps -l

jps vypíše všechna ID spuštěných procesů Java.

V systému Windows

C:Program FilesJavajdk1.8.0_171bin>jps -l
47172 portal
6120 sun.tools.jps.Jps
C:Program FilesJavajdk1.8.0_171bin>

V systému Linux

[[email protected] ~]# 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
[[email protected] ~]#

Výpis nám zobrazí seznam všech spuštěných Java procesů. V prvním sloupci je lokální ID virtuálního stroje a ve druhém sloupci název aplikace. Pro vygenerování výpisu vláken použijeme program jStack s příznakem –l, který vytvoří detailní výpis. Výstup můžeme také uložit do textového souboru.

jstack -l 26680

[[email protected] ~]# 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 [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"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 to wait for  <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$DelayedWorkQueue.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)

   Locked ownable synchronizers:
        - None

"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 to wait for  <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$DelayedWorkQueue.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)

   Locked ownable synchronizers:
        - None

#2. jvisualvm

jvisualvm je grafický nástroj, který se používá k monitorování a profilování Java aplikací. Je součástí JVM a je možné jej spustit z adresáře /bin instalace Java. Nástroj je uživatelsky přívětivý a snadno ovladatelný. Umožňuje nám také zachytit výpis vláken pro konkrétní proces.

Pro získání výpisu vláken u konkrétního procesu stačí na daný proces kliknout pravým tlačítkem myši a z kontextového menu vybrat možnost „Thread Dump“.

#3. jcmd

jcmd je nástroj příkazové řádky, který je součástí JDK a slouží k zasílání diagnostických příkazů do JVM.

Funguje pouze na lokálním počítači, kde běží Java aplikace. Můžeme jej použít pro správu Java Flight Recordings, diagnostiku a řešení problémů s JVM a Java aplikacemi. Příkazem Thread.print můžeme získat výpis vláken pro konkrétní proces určený pomocí PID.

Příklad použití jcmd:

jcmd 28036 Závit.tisk

C:Program FilesJavajdk1.8.0_171bin>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() [0x00000000244ef000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Unknown Source)
        at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.getNextEvent(EventManager.java:403)
        - locked <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 to wait for  <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$DelayedWorkQueue.take(Unknown Source)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.getTask(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x0000000021a7b000 nid=0x2184 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x00000000219f5000 nid=0x1300 waiting on condition [0x0000000000000000]
   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 [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x00000000219db800 nid=0x2260 waiting on condition [0x0000000000000000]
   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 [0x0000000000000000]
   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 (on object monitor)
        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(Unknown Source)
        at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000005806000 nid=0x13c0 in Object.wait() [0x00000000219af000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076f398178> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Unknown Source)
        at java.lang.ref.Reference.tryHandlePending(Unknown Source)
        - locked <0x000000076f398178> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)

"main" #1 prio=5 os_prio=0 tid=0x000000000570e800 nid=0xbf8 runnable [0x0000000000fec000]
   java.lang.Thread.State: RUNNABLE
        at java.util.zip.ZipFile.open(Native Method)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        at java.util.zip.ZipFile.<init>(Unknown Source)
        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(Native Method)
        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(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        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 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000005733800 nid=0x216c runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000005734800 nid=0xb930 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000000021a8d000 nid=0x2dcc waiting on condition

JNI global references: 14


C:Program FilesJavajdk1.8.0_171bin>

#4. JMC

JMC, neboli Java Mission Control, je open-source grafický nástroj, který je součástí JDK a slouží ke shromažďování a analýze dat Java aplikací.

Spouští se z adresáře /bin vaší instalace Java. Tento nástroj používají správci a vývojáři Java pro sběr detailních informací o chování JVM a aplikací. Umožňuje důkladnou a efektivní analýzu dat shromážděných pomocí Java Flight Recorder.

Po spuštění JMC vidíme seznam Java procesů běžících na lokálním počítači. Možné je také vzdálené připojení. Pro konkrétní proces můžeme kliknout pravým tlačítkem a vybrat „Spustit záznam letu“ a následně si v záložce Vlákna prohlédnout výpisy vláken.

#5. jconsole

jconsole je nástroj pro správu a monitorování Java Management Extension (JMX).

Má sadu předdefinovaných operací s agentem JMX, které může uživatel provádět. Umožňuje uživateli detekovat a analyzovat trasování zásobníku běžícího programu. Spouští se z adresáře /bin instalace Java.

S pomocí jconsole můžeme zkontrolovat trasování zásobníku každého vlákna po připojení k běžícímu procesu Java. V záložce Vlákna uvidíme názvy všech běžících vláken. Pro detekci uváznutí můžeme kliknout na „Detect Deadlock“ v pravém dolním rohu okna. Pokud je uváznutí detekováno, objeví se na nové záložce, jinak se zobrazí zpráva „No Deadlock“.

#6. ThreadMxBean

ThreadMXBean je rozhraní pro správu systému vláken virtuálního stroje Java, které patří do balíčku java.lang.Management. Používá se především pro detekci vláken, které se dostaly do uváznutí, a pro získání podrobností o nich.

Pro programové zachycení výpisu vlákna můžeme použít rozhraní ThreadMxBean. Metoda getThreadMXBean() z ManagementFactory se používá k získání instance rozhraní ThreadMXBean. Vrací počet aktivních démonických i ostatních vláken. ManagementFactory je třída pro získávání spravovaných beanů pro platformu 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 ();
}

Manuální analýza výpisů vláken

Analýza výpisů vláken je velmi užitečná při diagnostice problémů v multi-vláknových procesech. Problémy, jako jsou uváznutí, spory o zámky a nadměrné využití procesoru, můžeme řešit vizualizací stavů jednotlivých vláken ve výpisu.

Pro dosažení maximální propustnosti aplikace je důležité analyzovat výpis stavu vláken a následně opravit stav každého z nich.

Řekněme, že proces využívá hodně CPU. Můžeme zkontrolovat, které vlákno procesor nejvíce zatěžuje. Pokud takové vlákno najdeme, převedeme jeho číslo LWP na hexadecimální formát. Poté můžeme ve výpisu vláken najít vlákno s hodnotou „nid“, která odpovídá získané hexadecimální hodnotě. Pomocí trasování zásobníku vlákna můžeme určit zdroj problému. ID procesů vláken můžeme získat pomocí následujícího příkazu:

ps -mo pid,lwp,stime,time,cpu -C java

[[email protected] ~]# ps -mo pid,lwp,stime,time,cpu -C java
       PID        LWP         STIME           TIME              %CPU
26680               -         Dec07          00:02:02           99.5
         -       10039        Dec07          00:00:00           0.1
         -       10040        Dec07          00:00:00           95.5

Nyní se podívejme na výpis vláken. Pro získání výpisu vláken pro proces 26680 použijeme příkaz jstack -l 26680.

[[email protected] ~]# 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 [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

.
.
.
.
.
.
.
"<strong>Reference Handler</strong>" #2 daemon prio=10 os_prio=0 tid=0x00007f085814a000 nid=0x6840 in Object.wait() [0x00007f083b2f1000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        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)

   Locked ownable synchronizers:
        - None

"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 runnable

"VM Periodic Task Thread" os_prio=0 tid=0x00007f08581a0000 nid=0x6847 waiting on condition

JNI global references: 1553

Pojďme se podívat na to, jaké informace můžeme z výpisu vláken získat. Na první pohled může být výpis vláken zahlcující, ale pokud k němu přistoupíme krok za krokem, není obtížné mu porozumět. Začněme prvním řádkem:

27. 6. 2020 9:01:29
Úplný výpis vlákna Java HotSpot(TM) 64-Bit Server VM (25.221-b11 smíšený režim):

Tento řádek zobrazuje čas vygenerování výpisu a informace o použitém JVM. Následně, na konci výpisu, vidíme seznam vláken, přičemž první je vlákno ReferenceHandler.

Analýza blokovaných vláken

Analýza protokolů výpisů vláken nám může pomoci identifikovat vlákna ve stavu BLOCKED, která mohou zpomalovat výkon aplikace. Pokud tedy najdeme BLOKOVANÁ vlákna, můžeme se podívat na vlákna, která souvisejí se zámky, o které se daná vlákna snaží získat. Analýza trasování zásobníku vlákna, které aktuálně drží zámek, nám může pomoci problém vyřešit.

[[email protected] ~]# jstack -l 26680
.
.
.
.
" DB-Processor-13" daemon prio=5 tid=0x003edf98 nid=0xca waiting for monitor entry [0x000000000825f000]
java.lang.Thread.State