jstack 线程栈与 Java 线程状态分析
更新时间:2026-09-18。配套工具文:jstack 线程 Dump 分析实战。本篇把线程状态和怎么从栈看出问题讲清。
一、导出 Dump
bash
jps # 找 Java 进程 PID
jstack -l <pid> > t1.txt # -l 打印额外锁信息;生产建议连采 3 次间隔 5s
jcmd <pid> Thread.print # JDK8+ 等价方式
# 容器内:jcmd/jstack 需在目标容器命名空间执行连采多次是为了对比栈是否在变化:多次都停在同一行,才说明真卡住。
二、六种线程状态怎么读
| jstack 状态 | 含义 | 常见根因 |
|---|---|---|
RUNNABLE | 可运行(可能正在跑,也可能在做 native IO/网络读) | CPU 高看它;卡在 socketRead 其实是 IO 等待 |
BLOCKED | 等 synchronized 锁 | 锁竞争、被某线程长期持锁 |
WAITING | 无限等待(Object.wait、LockSupport.park) | 线程池空闲、等任务、等通知 |
TIMED_WAITING | 限时等待(sleep、带超时 wait/park) | 正常休眠、超时等待 |
| 死锁段 | Found one Java-level deadlock | 互相等锁 |
关键经验:大量 WAITING (parking) 在线程池空闲队列上是正常的;真正的问题要看业务线程在等什么资源。
三、三类典型问题定位
1. CPU 飙高
bash
top -Hp <pid> # 找耗 CPU 最高的线程 tid(十进制)
printf '%x\n' <tid> # 转成十六进制 nid
jstack <pid> | grep nid=0x<nid> -A30 # 在 Dump 中找到该线程栈栈顶是业务计算 → 死循环/正则/复杂计算;是 GC 线程 → 配合 jstat 看 GC。
2. 接口整体卡住
看大量业务 HTTP 线程是否 BLOCKED 在同一把锁、或全 WAITING 在某个外部调用(下游慢、连接池无可用连接)。这往往是雪崩前兆,见 服务雪崩怎么防止。
3. 死锁
Dump 末尾直接给出 Java-level deadlock,列出「线程 A 持锁 1 等锁 2、线程 B 持锁 2 等锁 1」,按持锁顺序改代码即可。
四、常见坑
- 只看一次 Dump 下结论:RUNNABLE 可能只是恰好经过,要连采 2~3 次比对。
- 把 socketRead 的 RUNNABLE 当 CPU 忙:Java 做阻塞 IO 时线程显示 RUNNABLE 但实际在等网络,要看栈顶是不是
socketRead0。 - 线程没名字无法归因:线程池一定要用自定义
ThreadFactory命名(如order-pool-%d),否则 Dump 里全是pool-123-thread-4,无法对应业务。 - 忽略
-l和锁的 ownable synchronizers:jstack -l才打印ReentrantLock等 AQS 锁持有信息,排查Lock死锁时不能省。
总结
jstack -l 导出线程 Dump 后先认状态:RUNNABLE 要看栈顶是真计算还是 native IO 等待,BLOCKED 是在等 synchronized,WAITING/TIMED_WAITING 是 park/wait/sleep,线程池空闲线程的 WAITING 属正常;CPU 飙高用 top -Hp 找高耗线程、tid 转十六进制后回 Dump 定位栈顶,接口卡死看业务线程是否集体 BLOCKED 同一锁或 WAITING 外部慢调用,死锁直接看 Dump 末尾的 deadlock 段;务必连采多次比对、给线程池命名、排查 AQS 锁加 -l,并警惕 socketRead 显示 RUNNABLE 的假象。