jstack是最快最准的死锁初筛工具,能直接输出线程状态并明确标识死锁;JConsole提供图形化检测与源码定位;ThreadMXBean支持编程式主动监控;修复需避免伪安全,注重锁对象一致性、超时回滚及压测验证。

用 jstack 一眼揪出死锁线程
只要 JVM 进程还在跑,jstack 就是最快最准的“死锁初筛器”。它不依赖图形界面,也不需要提前埋点,直接输出当前所有线程状态,且对死锁有明确标识。
- 先用
jps -l找到目标 Java 进程 PID(比如12345) - 再执行
jstack 12345 | grep -A 10 "Found one Java-level deadlock"—— 如果有死锁,立刻命中,后面紧跟着的就是互相卡住的两个(或多个)线程堆栈 - 重点看每段堆栈末尾的
waiting to lock和locked行,它们暴露了谁在等谁的锁、锁对象哈希值是否一致
注意:jstack 必须由与目标进程相同用户运行,否则可能权限拒绝;生产环境若禁止 jstack,说明 JVM 启动时加了 -XX:+DisableAttachMechanism,得提前协调放开。
用 JConsole 图形化确认并定位代码行
当命令行不够直观,或者需要快速向同事演示死锁现场时,JConsole 是最轻量的可视化选择——它自带“检测死锁”按钮,点击即出结果,还能直接跳转到源码行号。
- 启动
jconsole,选中目标进程连接后,切到“线程”页签 - 点“检测死锁”,如果存在,会弹窗提示,并在下方列出死锁线程名、状态、以及关键堆栈帧(含文件名和行号,如
DeadLock.java:32) - 双击任一死锁线程,可展开完整堆栈,对照源码就能看到是哪个
synchronized块或lock.lock()调用卡住了
陷阱:JConsole 默认只连本地进程;远程连接需 JVM 启动时加 -Dcom.sun.management.jmxremote 等参数,且网络端口要放行,别等到线上出问题才补配置。
立即学习“Java免费学习笔记(深入)”;
用 ThreadMXBean.findDeadlockedThreads() 主动监控
想让应用自己“察觉”死锁并告警?不用等运维介入,靠 ThreadMXBean 编程式检测即可。它适合嵌入健康检查接口、定时任务或 APM 告警逻辑中。
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] ids = bean.findDeadlockedThreads();
if (ids != null && ids.length > 0) {
ThreadInfo[] infos = bean.getThreadInfo(ids, true, true);
for (ThreadInfo info : infos) {
System.err.println("DEADLOCK DETECTED: " + info.getThreadName());
// 可记录日志、发钉钉、触发 dump 等
}
}
注意:findDeadlockedThreads() 只检测 Java 层锁(synchronized / java.util.concurrent),不包括 JNI 或操作系统级锁;另外它开销极小,但频繁调用(如每秒一次)仍可能轻微影响性能,建议 10–30 秒间隔为宜。
修复死锁不能只改顺序,还得防“伪安全”
按固定顺序加锁(比如永远先 lockA 再 lockB)确实是经典解法,但实践中常踩两个坑:一是锁对象本身被动态创建,导致“顺序”失效;二是用了 ReentrantLock.tryLock(timeout) 却没处理超时失败的回滚路径。
- 避免用 new Object() 当锁对象——不同实例哈希值不同,看似顺序一致,实则锁根本不是同一把
- 用
tryLock(1, TimeUnit.SECONDS)时,必须配套释放已获锁,否则可能引发“部分加锁 + 长期阻塞”新问题 - 更稳妥的做法是提取锁对象为 static final,或用资源 ID 做全局锁池(如
ConcurrentHashMap.computeIfAbsent(id, k -> new ReentrantLock()))
真正难的不是发现死锁,而是确认修复后没有引入新的竞争边界——比如把双锁改成单锁,却导致吞吐量断崖下跌。上线前务必压测对比 QPS 和平均延迟。








