jstack -l 是检测 Java 层死锁最轻量手段,可识别 synchronized 和 ReentrantLock 的循环等待,需检查 locked/waiting to lock 地址交叉匹配,且线程须处于 BLOCKED 或 WAITING 状态。

死锁检测的关键在于识别线程之间对对象监视器(Monitor)的交叉等待关系。jstack 是最轻量、最直接的手段,但它只对 Java 层的 synchronized 和 java.util.concurrent.locks.Lock 有效,不涉及 JNI、文件锁或数据库连接等外部资源。
必须用 -l 参数才能准确定位锁归属
jstack 默认输出不显示 ReentrantLock 的持有者和等待者信息,容易漏判。加 -l 后,每个锁会明确标注:
- locked :该线程正持有这个监视器地址的锁
- waiting to lock :该线程正阻塞等待这个地址的锁
- 若出现 A 等待 B 持有的锁,B 又等待 A 持有的锁,地址交叉匹配,即构成循环等待
死锁提示只出现在日志末尾,且仅当 JVM 真正捕获到闭环
“Found 1 deadlock” 这类提示不会穿插在中间,固定位于 jstack 输出的最后几行。但要注意:
- 没有提示 ≠ 没有死锁:可能是锁类型不被支持(如自定义非阻塞同步),或线程状态不是 BLOCKED/WAITING(比如已进入 native 调用)
- 线程必须处于 BLOCKED(等待 monitor entry)或 WAITING(parking on condition)状态,才可能被 JVM 判定为参与死锁
- 单纯 sleep、RUNNABLE 或 TIMED_WAITING 不触发死锁检测逻辑
重定向 + grep 是高效筛查的标配操作
生产环境线程数多,控制台输出易截断。推荐流程:
- 执行
jstack -l保存完整快照> thread.log - 用
grep -A 10 -B 5 "waiting to lock\|locked" thread.log快速提取关键锁段 - 比对地址是否成对出现:例如线程 T1
locked且waiting to lock,而线程 T2 正好相反
确认目标进程状态再排查
jstack 对象必须是活跃的 Java 进程:
- 用
ps aux | grep java核对 PID 是否存在,排除僵尸(zombie)或已挂起进程 - 避免在 GC 压力极大时 dump(如频繁 Full GC),虽不影响检测逻辑,但可能掩盖真实线程行为
- -XX:+UseParallelGC 等 GC 参数对死锁检测无影响,无需调整 JVM 启动参数来辅助排查











