Capture and analyze Java thread dumps — detect deadlocks, lock contention, pool starvation, and blocked I/O in production.
jstack reports Java deadlocks explicitly — instant RCA for hung services.
See all 200 threads BLOCKED on one lock or I/O wait.
Thread dumps have minimal overhead — capture every few minutes during incidents.
What thread dumps are and when to capture them.
Hangs and latency without error logs — thread dumps reveal the wait.
Platform vs virtual threads, thread pools, and monitors.
jstack, jcmd, kill -3, and automation.
Thread headers, stack frames, and lock sections.
RUNNABLE, BLOCKED, WAITING, TIMED_WAITING meanings.
Automatic jstack detection and manual cycle analysis.
Many threads BLOCKED on one monitor — the hot lock.
Tomcat, @Async, ForkJoin, and custom executors.
Three-layer diagnosis: JVM threads, JFR events, kernel syscalls.
kubectl exec and ephemeral debug containers.
@Async, @Scheduled, Tomcat, WebFlux event loops.
JDBC, HTTP client, and messaging waits in stack traces.
Capture frequency, automation, and storage.
Real thread dump investigations.
jstack vs JFR vs strace decision guide.
Thread incident response workflow.
Production SRE playbooks, examples, and incident patterns
A thread dump is a snapshot of every thread's stack trace, state, and lock information at one instant. Essential for hangs, deadlocks, and pool exhaustion.
| Incident | Metrics | Thread Dump Shows |
|---|---|---|
| API hung | Threads maxed, no CPU | 200 threads BLOCKED on HikariCP.getConnection |
| Deadlock | Zero throughput | Found Java-level deadlock: thread A ↔ B |
| Slow recovery | Gradual latency | TIMED_WAITING on HTTP client read |
jcmd <PID> Thread.print > /tmp/td-$(date +%s).txt jstack -l <PID> > /tmp/td.txt # -l = extra lock info kill -3 <PID> # SIGQUIT — dumps to stdout/logs
Capture 3 dumps 30 seconds apart during incidents — distinguishes stuck vs slow-progress threads.
"http-nio-8080-exec-12" #45 daemon prio=5 os_prio=0 cpu=123ms elapsed=45s tid=0x... nid=0x... waiting on condition java.lang.Thread.State: BLOCKED (on object monitor) at com.zaxxer.hikari.pool.HikariPool.getConnection(...) - waiting to lock <0x00000007b2c4a8f0> (a com.zaxxer.hikari.pool.HikariPool)
Thread name → State → Stack frames (most recent at top) → Lock info at bottom of thread block.
jstack prints Found one Java-level deadlock with the cycle of threads and locks.
Search dump for BLOCKED and group by lock address <0x...>. The lock with most waiters is your bottleneck.
http-nio-*-exec-* BLOCKED → Tomcat workers stuckpool-N-thread-M WAITING → custom pool idle or blocked on taskIf every worker is BLOCKED on external I/O, increasing pool size only delays failure — fix the dependency.
| Tool | Shows |
|---|---|
| Thread dump | Which Java method / lock threads wait on |
| JFR | JavaMonitorEnter duration, thread timeline |
| strace | futex wait, socket read block at kernel |
kubectl exec <pod> -- jstack 1 > thread-dump.txt kubectl exec <pod> -- jcmd 1 Thread.print # distroless: kubectl debug -it <pod> --image=amazoncorretto:17 --target=app --share-processes
http-nio-* — servlet blocking calls kill throughput@Async pool threads — separate from HTTP workersImpact: Complete payment outage 18 min
Symptoms: Zero throughput, CPU idle, no errors
Dump evidence: jstack: deadlock between PaymentService.lock and LedgerService.lock
Root cause: Inconsistent lock ordering across two services in same JVM
Resolution: Global lock order: always Ledger then Payment; deployed hotfix
Lesson: jstack deadlock section gave exact threads — fix in 15 min
Impact: API timeouts, 503 errors
Symptoms: 200/200 Tomcat threads busy
Dump evidence: All http-nio-exec BLOCKED on HikariPool.getConnection — pool timeout 30s
Root cause: DB failover caused 30s connection attempts holding all workers
Resolution: Reduced connection timeout to 3s; circuit breaker on DB
Lesson: Thread dump showed pool issue faster than DB team escalation
Impact: Consumer lag 5M messages
Symptoms: One consumer pod stuck, others fine
Dump evidence: kafka-consumer-thread BLOCKED on ReentrantLock held by same thread — recursive lock bug
Root cause: Custom interceptor re-entered synchronized block
Resolution: Replaced synchronized with ReentrantLock + removed recursion
Lesson: Single stuck thread in dump — not always all threads
Impact: API slow every hour on the hour
Symptoms: Predictable latency spike
Dump evidence: scheduling-1 RUNNABLE in heavy loop; 50 http threads BLOCKED on shared StaticLock
Root cause: @Scheduled job held global synchronized lock for 4 minutes during report generation
Resolution: Moved report to @Async with dedicated pool; removed global lock
Lesson: Correlate latency spike time with scheduler thread in dump
Impact: Checkout dependency timeout cascade
Symptoms: TIMED_WAITING on socket read
Dump evidence: 80 threads in sun.nio.ch.SocketDispatcher.read waiting on inventory API
Root cause: Inventory service degraded; no circuit breaker on caller
Resolution: Added resilience4j timeout + fallback; reduced client read timeout
Lesson: TIMED_WAITING + socket read = external dependency — not JVM bug
Impact: Brief hang reported by QA
Symptoms: All threads RUNNABLE in dump but service unresponsive momentarily
Dump evidence: Dump taken during long GC pause — threads runnable but JVM paused
Root cause: Full GC 8s pause — not thread issue
Resolution: Tuned G1; used JFR for GC not thread dump
Lesson: Thread dump during STW GC is misleading — check GC logs too
# Incident script for i in 1 2 3; do jcmd $PID Thread.print > /tmp/td-$i.txt; sleep 30; done diff /tmp/td-1.txt /tmp/td-3.txt # stuck = identical stacks
Safe capture during live incident.
Parse 'Found one Java-level deadlock' section.
All workers BLOCKED — find the monitor.
Threads waiting on socket read to PostgreSQL.
kubectl exec + jstack workflow.
Stuck vs progressing threads.
10 questions · Instant feedback
How to capture thread dump in production?
jcmd <PID> Thread.print or jstack <PID> — redirect to file. Repeat 2-3 times 30s apart.
How to find deadlock?
Search for 'Found one Java-level deadlock' or analyze lock chains manually in dump.
What causes all Tomcat threads BLOCKED?
Slow DB, external API, lock contention, or pool misconfiguration.
THREAD dump during high CPU?
Still useful — see if RUNNABLE threads are in tight loop vs blocked.
How to analyze thread pool sizing?
Compare active vs max threads in dump; count WAITING on queue vs RUNNABLE.
jstack vs jcmd Thread.print?
Equivalent output on modern JDK; jcmd preferred unified tool.
Thread dump in Kubernetes?
kubectl exec + jstack 1 or jcmd 1 Thread.print; ephemeral container if needed.
No progress on requests → Thread dump x3 → All BLOCKED? → Find lock/socket → Deadlock? → Fix ordering or timeout
P99 up → Thread dump → Pool exhausted? → Scale pool or fix slow dep → Lock contention?
Complete hang → jstack → Deadlock section? → Fix lock order → Verify with new dump
Threads BLOCKED → Stack shows socket read JDBC → DB metrics → Query or pool fix
jcmd <PID> Thread.print > /tmp/td.txt jstack <PID> > /tmp/td.txt