必须用 jstack -l 生成带锁信息的快照,核心是定位“Found one Java-level deadlock”区块中线程间 locked 与 waiting to lock 地址的交叉环路,并据此回溯到源码具体行号。

直接用 jstack 查死锁,关键不是“有没有快照”,而是“怎么读快照里锁的依赖关系”。它本身不分析变量值,但能清晰暴露线程间谁持有什么锁、又在等什么锁——这才是定位死锁的核心依据。
一、快速生成带锁信息的线程快照
只运行 jstack 很可能漏掉关键锁线索。必须加 -l 参数,它会显式打印每个线程持有的监视器锁(locked )和正在等待的锁(waiting to lock ):
- 先用
jps -l找到目标 Java 进程 PID - 执行
jstack -l,把完整输出存成文件,避免终端截断> dump.log - 生产环境建议加
-F(强制模式),尤其当 JVM 响应迟钝时:jstack -F -l> dump.log
二、聚焦末尾的死锁检测报告
jstack 在输出最后会自动扫描并汇总死锁。只要看到这段内容,说明已确认存在 Java 层死锁:
<font size="2">Found one Java-level deadlock:<br>=============================<br>"Thread-1":<br> waiting to lock monitor 0x00007f8c8c0b5d60 (object 0x00000007d5a3a0a0, a java.lang.Object),<br> which is held by "Thread-0"<br>"Thread-0":<br> waiting to lock monitor 0x00007f8c8c0b8d60 (object 0x00000007d5a3a0d0, a java.lang.Object),<br> which is held by "Thread-1"</font>
这里明确给出了两个线程、各自等待的对象地址(0x00000007d5a3a0a0 和 0x00000007d5a3a0d0),以及谁在持有谁——无需反编译,直接对应到代码里的两个 synchronized 块或 ReentrantLock 实例。
三、顺藤摸瓜定位具体代码位置
找到死锁线程名后(如 "Thread-0"),回到 dump 文件中搜索该线程的完整堆栈段。重点看两处:
- 看它当前停在哪一行:例如
at com.example.Service.updateOrder(Service.java:42),这行大概率是第二个synchronized或lock()调用点 - 再往上翻,找它最早获得锁的地方:比如
- locked (a java.lang.Object)出现在Service.java:35,说明它先锁了 A,再试图锁 B - 同理查
"Thread-1"的locked和waiting to lock行,就能还原出“线程A锁A等B,线程B锁B等A”的闭环
四、常见干扰项识别与排除
不是所有阻塞都叫死锁。这些状态容易误判,需结合上下文区分:
-
BLOCKED:线程想进
synchronized方法但锁被占,属于锁竞争,不一定死锁 -
WAITING (parking):常见于
ThreadPoolExecutor空闲线程,或Condition.await(),属正常等待 -
TIMED_WAITING (sleeping):调用了
Thread.sleep()或带超时的wait(),通常无害 - 多个线程都显示
WAITING却没死锁提示?可能是任务队列满、下游服务不可用导致的“假死”,要查日志和外部依赖











