jstack -l抓取线程快照后应聚焦三处:死锁声明位置(末尾found one java-level deadlock)、线程blocked状态及锁地址匹配(waiting to lock与locked地址一致)、synchronized与reentrantlock锁的区分(后者看locked ownable synchronizers),再结合nid和源码行号定位加锁点。

用 jstack -l <pid></pid> 抓取线程快照后,关键不是通读全部堆栈,而是聚焦三处信息:死锁声明位置、线程阻塞状态、锁地址匹配关系。
看输出开头有没有明确死锁提示
执行 jstack -l 12345 后,直接滚动到最末尾。如果存在死锁,JVM 会原样打出:
- Found one Java-level deadlock:
- 下面紧跟着列出所有卷入死锁的线程名
- 每条线程都标明“waiting to lock which is held by 'XXX'”
- 这个提示只在 JDK 8u60+ 稳定出现;老版本可能不显示或仅隐含在堆栈中
识别线程的 BLOCKED 状态和锁对象地址
在每个线程的堆栈段里,重点找这两行:
- java.lang.Thread.State: BLOCKED (on object monitor) —— 表示它正卡在 synchronized 进入点
- - waiting to lock —— 它想抢的锁地址
- - locked —— 它自己已经攥着的锁地址
把 Thread-A 的 “waiting to lock” 地址,跟 Thread-B 的 “locked” 地址比对,完全一致就确认是互锁关系。
区分 synchronized 锁和 ReentrantLock 锁
两者在 jstack 输出中表现不同,不能混看:
-
synchronized 锁:靠
waiting to lock和locked后面的十六进制地址匹配,对象类型通常是java.lang.Object或具体业务类实例 -
ReentrantLock:要看 Locked ownable synchronizers 区域,里面会写明
- java.util.concurrent.locks.ReentrantLock$NonfairSync@abcd1234,并标注 owner thread ID - 如果看到
parking to wait for,那是 AQS 队列阻塞,不等于死锁,别误判
结合 nid 和源码定位具体代码行
每个线程头都有 nid=0x...(十六进制本地线程 ID),而堆栈末尾通常带源码文件与行号,例如:
at com.example.Account.transfer(Account.java:42)at com.example.TransferTask.run(TransferTask.java:28)
顺着这行号打开对应代码,就能看到是哪一行 synchronized(obj) 或 lock.lock() 导致了锁获取。再检查该方法里是否调用了另一个加锁操作,或是否与其他线程采用了不一致的加锁顺序。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











