直接看线程堆栈日志可定位大多数死锁和阻塞问题,关键抓三类线索:状态异常的线程(WAITING/BLOCKED)、等待锁的对象地址、类初始化卡点;需横向比对锁持有关系识别循环等待,结合kill-3与业务日志联动分析。

直接看线程堆栈日志就能定位大多数死锁和阻塞问题,关键不是等出事再救火,而是从 jstack 输出里抓准三类线索:状态异常的线程、等待锁的对象、类初始化卡点。
重点关注 WAITING / BLOCKED 线程堆栈
运行 jstack -l <pid> 获取线程快照后,优先筛选以下状态:
-
WAITING (on object monitor):说明线程在等某个 synchronized 锁释放,要顺藤摸瓜看它 waiting to lock 的是哪个对象(比如
waiting to lock <0x0000000712345678>) - BLOCKED on <0x...>:明确指出被哪个锁阻塞,括号里就是锁对象地址,可结合代码查该地址对应哪一行静态字段或实例
-
java.lang.Class for XXX:如果看到类似
waiting to lock <0x...> (a java.lang.Class for com.example.Service),大概率是类初始化死锁,不是普通锁竞争
识别类初始化引发的“伪死锁”
静态代码块里启动线程,又在新线程里访问未初始化完成的类,就会触发 JVM 类加载器级阻塞——表面像死锁,实际是初始化锁没释放。
- 用 -XX:+TraceClassInitialization 启动 JVM,日志会打印每个类开始/结束初始化的线程名和时间
- 若发现线程 A 正在初始化 ClassA,而线程 B(刚 start 的)调用了 ClassA 的静态字段或方法,且 ClassA 初始化又依赖 ClassB,ClassB 又反过来引用 ClassA,就构成初始化闭环
- 常见陷阱:System.out.println()、SLF4J 日志、Apache Commons 工具类调用,它们内部可能触发额外类加载
交叉比对多个线程的锁持有关系
死锁本质是循环等待,单看一个线程看不出问题,必须横向对比:
立即学习“Java免费学习笔记(深入)”;
- 找到两个及以上线程都处于 BLOCKED 或 WAITING 状态
- 查每个线程的 “locked <0x...>” 和 “waiting to lock <0x...>”,看是否形成 A→B→A 这样的环
- 例如:线程 T1 locked <0x123>,waiting to lock <0x456>;线程 T2 locked <0x456>,waiting to lock <0x123>;这就是典型互斥锁死锁
- JConsole 或 VisualVM 会自动标出 Deadlock detected,但 jstack -l 的输出更底层、更可靠
辅助手段:用 kill -3 + 日志上下文联动
线上环境不方便频繁 jstack,可用 kill -3 <pid> 触发线程 dump 到 stdout 或 catalina.out(Tomcat 默认),配合业务日志时间戳缩小范围:
- 查到某次请求超时发生在 14:22:05,立刻翻 14:22 前后的 dump 日志
- 关注堆栈里是否出现业务关键类(如 Controller、Service 方法),确认阻塞是否发生在你的代码路径上
- 注意 TIMED_WAITING 不一定是问题,但若大量线程卡在同一个锁或同一段数据库连接获取逻辑,就是资源瓶颈信号


















