jstack线程分析
线程问题是 Java 线上故障中非常常见的一类。jstack 可以帮助我们查看 Java 进程中每个线程正在做什么。
# 1. 生成线程转储
jstack -l <pid> > thread.dump
建议连续抓取多次:
jstack -l <pid> > thread-1.dump
sleep 5
jstack -l <pid> > thread-2.dump
sleep 5
jstack -l <pid> > thread-3.dump
单次线程栈只是瞬时状态,多次对比才能看趋势。
# 2. 线程状态
常见线程状态:
RUNNABLE:正在运行或等待 CPU。BLOCKED:等待进入 synchronized。WAITING:无限期等待。TIMED_WAITING:限时等待。NEW:尚未启动。TERMINATED:已结束。
注意:RUNNABLE 不一定真的在占 CPU,也可能在 native IO 中等待。
# 3. 排查死锁
jstack 能检测 Java 层面的死锁。
常见输出:
Found one Java-level deadlock:
死锁通常表现为:
- 线程 A 持有锁 1,等待锁 2。
- 线程 B 持有锁 2,等待锁 1。
修复思路:
- 固定加锁顺序。
- 减少嵌套锁。
- 使用超时锁。
- 拆分临界区。
# 4. 排查线程池耗尽
如果大量线程处于等待远程调用、数据库、锁或队列状态,可能导致线程池耗尽。
关注:
- 线程名。
- 线程池前缀。
- 栈顶方法。
- 是否大量相同调用栈。
- 是否阻塞在外部资源。
线程名规范非常重要。没有清晰线程名,排查会很痛苦。
# 5. 排查 CPU 飙高
CPU 飙高时,jstack 要结合系统线程 ID 使用。
常见步骤:
top -Hp <pid>找到高 CPU 线程。- 把线程 ID 转成十六进制。
- 在线程转储中搜索
nid=0x...。 - 查看对应 Java 调用栈。
# 6. 常见栈顶含义
如果栈顶是:
Object.wait:线程在等待通知。Thread.sleep:线程睡眠。Unsafe.park:常见于 Lock、线程池、队列等待。- Socket read:可能在等待网络数据。
- JDBC 调用:可能等待数据库。
- 正则相关方法:可能是复杂正则导致 CPU 高。
# 7. 阶段小结
jstack 是分析线程状态的核心工具。抓线程栈时要多次采样,结合 CPU、线程池、业务日志和请求时间线判断。线程栈不是答案本身,它是定位问题的证据。
# 8. 线程状态判断表
| 状态 | 常见含义 | 重点排查 |
|---|---|---|
RUNNABLE | 运行中或 native 等待 | 是否高 CPU,栈顶是什么 |
BLOCKED | 等 synchronized 监视器 | 锁持有者是谁 |
WAITING | 无限等待 | 是否正常等待队列/条件 |
TIMED_WAITING | 限时等待 | sleep、park、超时等待 |
NEW | 未启动 | 一般不常见 |
TERMINATED | 已结束 | dump 中少见 |
# 9. CPU 高定位流程
top 发现 Java 进程 CPU 高
│
├─ top -Hp <pid> 找高 CPU 线程
│
├─ printf "%x\n" <tid> 转十六进制
│
├─ jstack -l <pid> 抓线程栈
│
├─ 搜索 nid=0x...
│
└─ 分析该线程栈顶方法
要连续抓取多次,如果同一线程一直停留在同一段代码,说明热点更可信。
# 10. Tips 快问快答
Q:BLOCKED 和 WAITING 有什么区别?
A:BLOCKED 通常是在等进入 synchronized;WAITING 是已经主动等待某个条件或通知。
Q:Unsafe.park 一定有问题吗?
A:不一定。线程池空闲、Lock 等待、队列等待都可能看到 park,要结合线程数量和业务状态判断。
Q:死锁一定能被 jstack 发现吗?
A:Java 层 monitor 死锁通常能发现,但数据库锁、分布式锁、外部资源等待不一定能直接识别为 deadlock。
Q:线程栈里看不到业务代码怎么办?
A:可能在 native、JIT 编译代码或采样时机不对,可以多次采样或使用 JFR/profiler。
上次更新: 2026/06/24, 16:03:01