jstack仅能检测java层synchronized和lock的循环等待死锁,对jni、文件锁等无效;死锁信息固定位于输出末尾,需比对waiting to lock与locked的监视器地址是否交叉;建议用jstack -l重定向后grep筛查。

死锁没打印堆栈?先确认 jstack 是否真能捕获到
很多情况下你执行了 jstack <pid></pid>,但输出里压根没有 “Found 1 deadlock” 提示,不是工具失效,而是 JVM 没检测到符合标准的循环等待——它只识别由 java.util.concurrent.locks.Lock 和内置 synchronized 构成的、且线程状态为 BLOCKED 或 WAITING 的闭环。如果死锁涉及 JNI、文件锁、数据库连接或自定义非阻塞同步逻辑,jstack 就完全看不到。
- 运行前加
-XX:+UseParallelGC或其它非默认 GC 不影响死锁检测,但-XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput这类诊断开关也帮不上忙 - 必须确保目标进程是正在运行的 Java 进程(
ps aux | grep java看 PID),不是已挂起或僵死的 zombie 进程 - 如果用
jstack -l <pid></pid>,会额外显示java.util.concurrent锁的持有者和等待者,比默认输出更准,建议养成加-l的习惯
jstack 输出里怎么快速定位死锁线索
别从头扫日志。死锁信息永远在最底部,且只有两段固定格式:一段是 “Found 1 deadlock.”,紧接着是每个死锁线程的完整堆栈快照,中间用空行隔开。关键不是看“在哪个方法卡住”,而是比对“谁持有什么锁”和“在等什么锁”。
- 找关键词:
waiting to lock表示该线程想获取某个对象监视器;locked表示它当前正持有这个锁 - 两个线程的
waiting to lock和locked地址如果交叉出现(比如 A 等 B 持有的锁,B 又等 A 持有的锁),就是典型死锁 - 注意地址值是否一致:
是对象监视器 ID,不是内存地址,同一 JVM 内重复出现即代表同一把锁
控制台日志太刷屏?用重定向 + grep 快速过滤
直接敲 jstack <pid></pid> 在终端里跑,容易被其他日志冲掉关键段,而且没法回溯。必须把输出落地再处理。
- 推荐命令:
jstack -l <pid> > jstack.out 2>&1</pid>,这样连可能的错误(如权限拒绝)也一并捕获 - 快速筛查:用
grep -A 20 "Found 1 deadlock" jstack.out直接拿到死锁上下文;若没结果,再试grep -E "waiting to lock|locked" jstack.out | head -20看是否有可疑锁竞争 - 别用
tail -f实时盯jstack输出——它是一次性快照,不是流式日志,实时盯没意义
为什么本地复现不了,线上却频繁死锁?
根本原因不是代码不同,而是线程调度时机和锁获取顺序的概率差异。本地单核或低并发下,两个线程几乎不会“恰好”以相反顺序抢两把锁;而线上多核+高并发,这种竞态窗口被极大放大。
- 检查是否用了静态工具类里的双重检查单例(
Double-Checked Locking),尤其在getInstance()中混用synchronized和volatile不当时极易触发 - 留意日志中反复出现的线程名(如
pool-2-thread-3),它们可能长期复用,导致锁状态累积;而本地每次都是新线程,状态干净 - 如果用了
CompletableFuture链式调用 + 自定义线程池,要特别注意thenApply和thenAccept是否跨线程访问了共享可变对象——jstack 看不到这种逻辑死锁,只能靠代码审计
真正难的从来不是看到死锁,而是判断哪一行代码让两个线程走上了互等的路径。堆栈只是终点,起点藏在锁的申请顺序里。








