Back to Thread Troubleshooting
Q5Question 5 of 5

How to capture and read a thread dump?

Key Concepts
  • A thread dump is a snapshot of every thread in a JVM: name, state, stack trace, and lock ownership.
  • Capture with jstack PID, kill -3 PID (prints to process stdout), or jcmd PID Thread.print.
  • jstack -l adds extra lock information including ReentrantLock ownable synchronizers.
  • Thread states: RUNNABLE (executing), BLOCKED (waiting for monitor), WAITING/TIMED_WAITING (parked).
  • Lock lines: 'locked <0x...>' means holds the monitor; 'waiting to lock <0x...>' means blocked on it.
  • The deadlock section lists the exact cycle of threads and locks when the JVM detects one.
  • Take 2-3 dumps seconds apart to distinguish genuinely stuck threads from normal busy work.

Definition

Capture a thread dump with jstack, kill -3, or jcmd, then read each thread's state and lock ownership to find the stuck thread or the deadlock section.
  • Thread dump – snapshot of every thread: name, state, call stack, locks held and awaited.
  • jstack – most common capture tool; -l adds lock details.
  • kill -3 – sends SIGQUIT; JVM prints the dump to its own stdout.
  • jcmd Thread.print – modern, preferred tool; supports -l for lock details.
  • States – RUNNABLE, BLOCKED, WAITING, TIMED_WAITING.
  • Lock lines – locked means holds it; waiting to lock means blocked trying to acquire it.
  • Deadlock section – the JVM prints the cycle when it detects one.

A thread dump is like freezing time in National Bank of India and photographing every teller: what each is doing, which vault key they are holding, and which key they are waiting for. If teller A holds the gold-vault key but waits for the cash-vault key while teller B holds the cash-vault key but waits for the gold-vault key, the photograph shows you the standoff: a deadlock.

Understanding the Concepts

How do you capture a thread dump?

Three standard methods, each useful in different situations.

BASH
# Most common: jstack with extended lock info
jps                            # find the PID of the Java process
jstack -l <pid> > dump.txt     # capture with lock details

# Alternative: SIGQUIT (prints to process stdout or log file)
kill -3 <pid>                  # does NOT kill the process

# Modern preferred: jcmd
jcmd <pid> Thread.print -l > dump.txt

Use jstack -l for the most detailed lock information. Use kill -3 when you cannot attach to the JVM (security restrictions). Use jcmd for the most modern and flexible approach.

How do you read thread states?

Each thread in the dump has a state that tells you what it is doing.

| State | Meaning | Common Cause |
|-------|---------|--------------|
| RUNNABLE | Executing or ready to execute | Actually working or in native IO |
| BLOCKED | Waiting to acquire a monitor lock | synchronized contention |
| WAITING | Parked indefinitely | wait(), LockSupport.park(), ReentrantLock |
| TIMED_WAITING | Parked with timeout | sleep(), parkNanos(), tryLock(timeout) |

How do you read lock ownership?

Look for two key lines: locked means the thread holds the lock; waiting to lock means the thread is blocked trying to acquire it. Match the hex identity hash code across threads to find who holds what.

TEXT
"settlement-pool-thread-3" BLOCKED
  at com.bank.service.TransferService.transfer(TransferService.java:42)
  - waiting to lock <0x00000007c0045678> (a com.bank.model.Account)

"settlement-pool-thread-7" RUNNABLE
  at com.bank.service.TransferService.transfer(TransferService.java:38)
  - locked <0x00000007c0045678> (a com.bank.model.Account)

Thread 3 is BLOCKED waiting for the Account lock that thread 7 holds. Thread 7's stack trace shows it is executing a database query inside the synchronized block.

How do you map a hot OS thread to a Java thread?

In the thread dump, each thread has an nid field (native thread ID) in hexadecimal. Use top -H -p on Linux to find the OS thread consuming the most CPU, convert its PID to hex, and match it to the nid in the dump.

BASH
# Find the hot OS thread
top -H -p <pid>
# Say thread 12345 is using 99% CPU
printf '%x\n' 12345  # outputs 3039
# Search the dump for nid=0x3039
grep 'nid=0x3039' dump.txt

How do you read the deadlock section?

When the JVM detects a deadlock, it prints the exact cycle at the end of the dump.

TEXT
Found one Java-level deadlock:
=============================
"transfer-A":
  waiting to lock <0x...Account@priya>
  which is held by "transfer-B"
"transfer-B":
  waiting to lock <0x...Account@rajesh>
  which is held by "transfer-A"

Fix: impose a global lock ordering. Always lock the account with the smaller ID first.

JAVA
package com.bank.service;

import java.math.BigDecimal;

public class SafeTransferService {
    public void transfer(Account from, Account to, BigDecimal amount) {
        Account first = from.getId().compareTo(to.getId()) < 0 ? from : to;
        Account second = first == from ? to : from;
        synchronized (first) {
            synchronized (second) {
                from.debit(amount);
                to.credit(amount);
            }
        }
    }
}

Why take multiple dumps?

One dump is a snapshot. A thread that appears BLOCKED in one dump might be working normally a second later. Comparing two or three dumps taken 5-10 seconds apart shows which threads are moving forward and which are stuck at the same stack frame. Threads stuck at the same frame across multiple dumps are genuinely blocked.

What are common mistakes?

Using anonymous thread names (Thread-0, Thread-1) makes dumps unreadable. Always name your threads with a meaningful prefix via a custom ThreadFactory.

What are common interview follow-up questions?

  • How do you capture a heap dump versus a thread dump? Thread dump: jstack or jcmd Thread.print. Heap dump: jmap -dump:format=b,file=heap.hprof or jcmd GC.heap_dump heap.hprof.
  • How does jcmd Thread.dump_to_file work in newer JVMs? It writes the dump directly to a file, useful in containerized environments where stdout is not easily captured.
  • What is the difference between BLOCKED and WAITING? BLOCKED is waiting for a synchronized monitor. WAITING is parked by LockSupport, wait(), or join().

Visual Overview

DIAGRAM
  Thread Dump Reading:

  "settlement-pool-thread-3" BLOCKED
    at TransferService.transfer(:42)
    - waiting to lock <0x...Account@priya>

  "settlement-pool-thread-7" RUNNABLE
    at TransferService.transfer(:38)
    - locked <0x...Account@priya>
    - locked <0x...Account@rajesh>

  Deadlock Detection:
  Thread A: holds rajesh → waits priya
  Thread B: holds priya → waits rajesh
        ┌──────────────────┐
        │   DEADLOCK CYCLE │
        │  A → priya → B   │
        │  B → rajesh → A  │
        └──────────────────┘
  Fix: always lock smaller account ID first

Key Takeaway

A thread dump is the first tool I reach for when a service stalls. I capture it with jstack -l, jcmd Thread.print, or kill -3. I read the thread name, state, lock lines, and the deadlock section. Matching the hex lock identity across threads shows who holds what and who is waiting.

I always take two or three dumps seconds apart to distinguish stuck threads from normal work, and I name my threads so the dump is instantly readable.

Remember one sentence: capture a thread dump with jstack or jcmd, read the state and lock lines to find who is stuck and who holds the lock, and the deadlock section tells you the exact cycle to fix with a global lock ordering.

---

Share
Use ← → arrow keys to navigate