“found one java-level deadlock”是jvm确认死锁的结论,需结合后续线程栈中“waiting to lock”与“locked”的锁地址交叉匹配定位根因,而非仅关注该提示行。

怎么看 jstack 输出里的 Found one Java-level deadlock
这行提示不是警告,是结论——JVM 确实检测到了死锁,且已定位到所有涉事线程和锁对象。关键不是“有没有”,而是“哪几个线程卡在哪儿、争什么锁”。jstack 日志里紧随其后的线程栈片段才是根因入口。
- 只看
Found one Java-level deadlock这一行没用,必须向下翻,找到被标记为"waiting to lock"和"locked"的成对线程 - 每个死锁至少涉及两个线程,A 持有锁 L1 等 L2,B 持有锁 L2 等 L1;注意比对
java.lang.Thread.State: BLOCKED (on object monitor)后面的锁地址(如0x0000000712345678)是否交叉匹配 - 锁地址相同才代表是同一把锁;不同十六进制地址 = 不同对象,不能凑对分析
如何从线程栈反推 synchronized 锁对象和调用路径
死锁几乎都发生在 synchronized 块或方法上,而栈帧里不会直接写“锁的是 userMap”,得靠变量名、类名、行号和上下文猜出来。
- 看
at com.example.Service.updateUser(...)这类用户代码行,结合源码确认该行是否在synchronized块内,以及锁的是this、静态类、还是某个字段(如private final Object lock = new Object()) - 如果锁的是
this,栈里会显示- locked (a com.example.Service);如果是显式对象,通常会带字段名(JVM 无法显示字段名,但 IDE 调试时能看到,日志里只能靠代码位置推断) - 特别注意
java.util.HashMap或ArrayList等非线程安全集合被多线程直接加锁访问的场景——它们本身不带锁,锁的是包装它的对象或外部同步块
为什么 jstack 没报 deadlock 但程序卡住了?
Found one Java-level deadlock 只能发现 **互相等待 monitor 锁** 的循环等待。它漏掉三类常见“伪死锁”:
- IO 阻塞(如
SocketInputStream.read()卡住)、线程池耗尽、数据库连接池满——这些不涉及synchronized,jstack 显示WAITING或RUNNABLE,但实际不动 - ReentrantLock 显式锁(
lock.lock())未启用公平策略 + 无超时,可能活锁或饥饿,但 JVM 不识别为 deadlock - 嵌套锁顺序不一致但未形成闭环:比如 A→B→C→A 是死锁,A→B→C→D 就不是,哪怕 D 等待极久,jstack 也不会标出
用 jstack 分析时最容易忽略的细节
拿到日志第一反应不是画锁图,而是确认基础事实是否成立。
立即学习“Java免费学习笔记(深入)”;
- 必须用与进程同用户的权限执行
jstack <pid></pid>,否则可能只输出部分线程或报Unable to get pid of LinuxThreads manager thread - 生产环境别只抓一次快照——死锁可能瞬时发生又释放,建议连采 3–5 次,间隔 2 秒,看是否稳定复现同一组线程
- 注意 JDK 版本差异:JDK 8u60+ 才默认开启
ThreadMXBean.findDeadlockedThreads()检测;老版本即使真死锁也可能不打印那行提示
真正难的不是识别那两行锁地址,是搞清业务逻辑里为什么要按这个顺序抢这两把锁——而这个,jstack 日志里从来不会告诉你。











