Thread States
The core answer for "how do you debug 100% CPU":
Start at the OS layer with top -H -p <PID> to find which native thread is hot. Convert that thread ID to hex, then find it in jstack output via the nid= field. Take 3–5 dumps, 5–10 seconds apart — if the same thread is RUNNABLE with the same stack frame in every dump, you've found your culprit (infinite loop or hot computation). If CPU is from GC, jstat -gcutil will tell you quickly.
The BLOCKED → CPU question (a classic trick question):
BLOCKED threads consume essentially zero CPU — the OS parks them. So no, a blocked thread cannot directly cause high CPU. But there's an indirect path: the thread holding the lock might be CPU-intensive, and BLOCKED threads can pile up as a symptom. The real exception is a spinlock — while(!ready){} keeps a thread RUNNABLE and burns CPU, but that's not Java's BLOCKED state, it's a coding anti-pattern.
Deadlock vs. high CPU: These are often confused. Deadlocked threads are BLOCKED/WAITING — CPU is low or normal, but the app freezes and requests stop completing. If you see low CPU + hung requests, look for the "Found one Java-level deadlock" section at the bottom of your jstack dump.
Sample Buggy Dump
- How to Capture
Step 1 · Find PIDGet the Java process IDjps -l
# Or on Linux/Mac:
ps aux | grep javaStep 2 · JDK tooljstack (most common)jstack <PID> > dump1.txt
# Repeat 3–5 times
# 5s apart for patterns:
for i in 1 2 3; do
jstack <PID> > dump$i.txt
sleep 5
doneStep 3 · Kill signalSIGQUIT (Linux)kill -3 <PID>
# Dump prints to stdout
# Redirect app logs to
# capture itAlternate · JVisualVMGUI approach (JDK)jvisualvm
# Connect to process
# → Threads tab
# → Thread Dump buttonAlternate · JFRJava Flight Recorderjcmd <PID> JFR.start
duration=60s
filename=rec.jfr
# Open in JMC for
# CPU flame graphAlternate · Async ProfilerFlame graphs (best)./profiler.sh -d 30
-f flamegraph.html
<PID>
# Best for pinpointing
# hot methodsAlways take 3–5 dumps, 5–10 seconds apart. A thread RUNNABLE in all dumps = likely the culprit. A single dump is a snapshot — patterns matter.
# Or on Linux/Mac:
ps aux | grep java
# Repeat 3–5 times
# 5s apart for patterns:
for i in 1 2 3; do
jstack <PID> > dump$i.txt
sleep 5
done
# Dump prints to stdout
# Redirect app logs to
# capture it
# Connect to process
# → Threads tab
# → Thread Dump button
duration=60s
filename=rec.jfr
# Open in JMC for
# CPU flame graph
-f flamegraph.html
<PID>
# Best for pinpointing
# hot methods
Debug Workflow
Interview answer structure: Start with OS tools → correlate PID to thread → capture thread dumps repeatedly → look for RUNNABLE threads across dumps → check for locks, deadlocks, and GC overhead.Step 1 · OS LevelFind the hot process + threadtop -H -p <PID>
# Shows per-thread CPU. Note the hot TID in decimal.
printf '%x\n' <TID> # Convert to hex — matches jstack outputStep 2 · CorrelateMap OS thread ID → jstackjstack <PID> | grep -A 20 "nid=0x<hex_tid>"
# nid= in jstack is the native thread ID in hexStep 3 · Pattern checkTake multiple dumps, compare# If a thread is RUNNABLE in all 3–5 dumps → infinite loop / hot code path
# If threads are BLOCKED and growing → lock contention
# Check: "Found one Java-level deadlock" section at dump bottomStep 4 · Root causeClassify the problem# Infinite loop → RUNNABLE, same stack every dump # Deadlock → BLOCKED, circular lock chain # Lock contention → many BLOCKED on same monitor # GC pressure → GC threads RUNNABLE + high GC overhead # External I/O tight loop → RUNNABLE in SocketRead/SelectBonus · GC checkRule out GC causing CPUjstat -gcutil <PID> 1000 10
# GCT column growing fast → GC is burning CPU
# Check for memory leak causing constant GC
# Shows per-thread CPU. Note the hot TID in decimal.
printf '%x\n' <TID> # Convert to hex — matches jstack output
# nid= in jstack is the native thread ID in hex
# If threads are BLOCKED and growing → lock contention
# Check: "Found one Java-level deadlock" section at dump bottom
# GCT column growing fast → GC is burning CPU
# Check for memory leak causing constant GC
Can a BLOCKED thread cause high CPU?
Short answer: No, not directly. A BLOCKED thread is sitting idle waiting for a monitor — it uses virtually zero CPU. It is parked by the OS.Indirect contribution: If the thread holding the lock is doing something CPU-intensive (and that causes other threads to pile up as BLOCKED), the RUNNABLE lock-holder is the CPU consumer. Blocked threads are victims, not culprits.Spinlock exception: If your code uses busy-waiting (e.g. while(!ready){} instead of Object.wait()), the thread stays RUNNABLE and burns CPU — but that's not true Java BLOCKED state, that's a coding bug.Deadlock doesn't burn CPU — deadlocked threads are BLOCKED/WAITING, so CPU stays low. But your app freezes. Look for deadlocks when CPU is normal but requests stop completing.
while(!ready){} instead of Object.wait()), the thread stays RUNNABLE and burns CPU — but that's not true Java BLOCKED state, that's a coding bug.