用 jstack -l <pid> 可自动检测死锁并输出“Found 1 deadlock.”及交叉锁地址,漏看该段即无效;不加 -l 则无法识别锁状态,易误判。

直接用 jstack -l <pid>,它会在输出末尾明确告诉你有没有死锁,并列出所有参与循环等待的线程及其锁关系。关键不是看全量栈,而是盯住“Found 1 deadlock.”那一段——漏掉就白忙活。
必须加 -l 参数才能看到锁地址
不带 -l 的 jstack 只输出线程调用栈,完全不分析锁状态,更不会标出 locked 和 waiting to lock 地址。你得手动翻几百行 WAITING 线程,再比对 synchronized 块或 ReentrantLock 调用,极易误判或遗漏。加上 -l 后,JVM 会触发内置死锁检测器,自动扫描所有 monitor 和 java.util.concurrent.locks 锁实例(JDK 8u60+ 支持更完整)。
- locked <0x0000000712345678> 表示该线程当前持有这个对象监视器
- waiting to lock <0x0000000787654321> 表示它正卡在这儿,试图获取另一把锁
- 两个线程的 locked 和 waiting to lock 地址交叉出现(A 持 0x123 等 0x456,B 持 0x456 等 0x123),就是环路实锤
从栈帧快速定位到具体代码行
死锁区块里每个线程段都附带完整 stack trace。重点看最上面两行:一是线程状态行(如 java.lang.Thread.State: BLOCKED (on object monitor)),二是紧随其后的 - locked <0x...> 或 - parking to wait for <0x...>。再往下找 at 开头的源码行,比如 at com.example.Service.updateOrder(Service.java:42),这行就是它正在执行的 synchronized 方法入口,或是 lock() 调用点。
- 如果是 ReentrantLock,注意看 “Locked ownable synchronizers” 区块下的 owner thread,不是 monitor entry
- 如果看到 parking to wait for <0x...>,那是 AQS 队列阻塞,属于正常排队,不等于死锁
- 若用的是 StampedLock、自旋锁或 JNI 层锁,jstack -l 无法识别,需另查
没报 Found deadlock ≠ 没问题
“no deadlocks” 只代表没检测到经典循环等待型死锁。程序仍可能卡在其他地方:
- 单个线程 BLOCKED 在竞争一把锁上(无环,但高并发下大量线程排队)
- 线程处于 TIMED_WAITING 或 WAITING,实际卡在 socket.read()、数据库连接池获取、文件读写等 I/O 上
- 用了 LockSupport.park() 自定义同步逻辑,或第三方库绕过 JVM 锁机制
- JNI 调用中持 Java 锁进入 C 代码,再回调 Java,形成 Native 层死锁,jstack 完全不可见
线上使用要注意轻量和时效
jstack -l 会触发 JVM 全局 safepoint,频繁执行会加重 STW 延迟,影响业务。建议:
- 一次导出足够:jstack -l <pid> > dump_$(date +%s).log
- 避免在高峰期反复轮询,可配合监控告警(如请求超时突增 + CPU 突降)再触发
- 容器环境注意权限,可能需用应用运行用户执行,而非 root
- 若进程已僵死且 attach 失败,可用 jstack -F -l <pid> 强制获取(慎用,有风险)


















