ThreadMXBean 可主动检测 synchronized 死锁,但不支持 ReentrantLock;需通过固定加锁顺序、tryLock 超时等手段从源头防控死锁。

用 ThreadMXBean 主动检测死锁
Java 提供了标准 API 来实时检测 JVM 中是否存在死锁线程,核心是 java.lang.management.ThreadMXBean。它不是靠日志或事后分析,而是运行时主动扫描线程栈,识别出互相持有并等待对方锁的循环等待链。
关键点:
-
ThreadMXBean.findDeadlockedThreads()返回long[](死锁线程 ID 数组),返回null表示无死锁;findMonitorDeadlockedThreads()仅检查synchronized锁(不包括java.util.concurrent中的Lock) - 必须通过
ManagementFactory.getThreadMXBean()获取实例,该 Bean 默认启用,无需额外配置 - 该方法是轻量级快照,但频繁调用(如每秒多次)会影响性能,建议用于告警触发或定时巡检(如每 30 秒一次)
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] deadlockedIds = threadBean.findDeadlockedThreads();
if (deadlockedIds != null && deadlockedIds.length > 0) {
ThreadInfo[] infos = threadBean.getThreadInfo(deadlockedIds, true, true);
for (ThreadInfo info : infos) {
System.out.println("Deadlocked thread: " + info.getThreadName());
System.out.println("Stack trace:\n" + Arrays.toString(info.getStackTrace()));
}
}
为什么 jstack 有时看不到死锁?
不是所有死锁都能被 jstack 直接标为 "Found 1 deadlock." —— 它只报告“已确认进入死锁状态且处于阻塞态”的线程。如果死锁刚形成、线程尚未完全阻塞(例如正处在锁获取的临界路径中),或使用了 ReentrantLock.tryLock() 等非阻塞方式,jstack 可能只显示 WAITING 或 TIMED_WAITING,而不会打上死锁标签。
常见误判场景:
立即学习“Java免费学习笔记(深入)”;
- 线程在
Lock.lockInterruptibly()中被中断后重试,状态来回切换,jstack快照抓不到稳定死锁态 - 死锁涉及
StampedLock的乐观读/写锁组合,jstack不解析其内部状态 - JVM 参数
-XX:+PrintConcurrentLocks仅对java.util.concurrent锁有效,但不参与死锁判定逻辑
监控 ReentrantLock 死锁需要额外手段
ThreadMXBean 的 findDeadlockedThreads() 默认不检测 java.util.concurrent 类锁(如 ReentrantLock、ReentrantReadWriteLock),因为它们不基于 JVM monitor 机制。要覆盖这类锁,必须开启 JVM 参数:
-
-XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=jvm.log不起作用——这是误区 - 正确做法:启动时加
-Djdk.internal.vm.ci.enabled=true无效,真正有效的是-XX:+UseParallelGC等无关参数?错 - 实际可行方案只有两个:手动遍历
Thread.getAllStackTraces()解析park调用栈 + 锁持有关系,或使用java.util.concurrent.locks.LockSupport.getBlocker(Thread)配合getHoldCount()做静态分析(需自行建模依赖图)
更现实的选择:在业务中统一封装 ReentrantLock,记录每次 lock() 和 unlock() 的线程 ID 与锁对象哈希,配合定时 dump 检查循环等待。
生产环境死锁监控别只靠“发现”,要防住源头
检测只是兜底。真正容易被忽略的是:多个线程以不同顺序获取同一组锁,是绝大多数死锁的根源。比如线程 A 先锁 accountA 再锁 accountB,线程 B 反过来操作,就极易触发死锁。
可落地的防御措施:
- 所有跨资源加锁强制按固定顺序,例如按
id升序:先lock(min(id1, id2)),再lock(max(id1, id2)) - 避免在持有锁期间调用外部服务或数据库(可能阻塞并引发间接锁竞争)
- 用
tryLock(long, TimeUnit)替代无参lock(),超时后释放已持锁并重试,打破死锁必要条件 - Spring 项目中慎用
@Transactional嵌套 +synchronized方法,事务锁和 JVM 锁混合极易绕过监控
死锁不是“能不能检测到”的问题,而是“有没有让线程不得不等下去”的设计选择。监控代码写得再全,也比不上加锁顺序的一致性约束来得可靠。










