等待链分析仅反映线程阻塞依赖关系,闭环才表明死锁,但无法揭示根因(如SQL慢、锁未释放等),须结合代码逻辑与资源上下文综合判断。
等待链分析是排查程序假死与死锁的实用手段,但它本身不直接揭示根本原因——它只暴露“谁在等谁”的依赖关系。真正定位根因,必须把等待链结果和代码逻辑、资源使用上下文结合起来看。
先分清假死和真死锁
假死不等于死锁。常见假死包括:
- 单线程卡在无限循环或阻塞 I/O(如未设超时的 socket.read)
- 数据库查询慢、磁盘响应延迟,导致线程长时间等待但未形成环
- 线程池耗尽 + 任务排队阻塞,整体无响应但无锁竞争
- JNI 调用底层 C/C++ 代码卡住,JVM 层面看不到锁信息
而真死锁必须满足四个条件:互斥、持有并等待、不可抢占、循环等待。等待链里出现闭环(A→B→C→A),且所有环节都处于主动等待状态,才是典型死锁信号。
按环境抓取有效等待链快照
关键在“时机”和“权限”,错过就抓不到真实链:
- Windows:用 ProcDump 必须管理员权限运行 procdump -w -o -ma [pid] dump.dmp;-o 表示只在检测到等待链时转储,但若卡顿短暂,建议改用 -s 1 -n 5 多次采样
- Linux:无原生等待链工具,需组合使用:gdb attach [pid] → thread apply all bt 查各线程栈,再对照 /proc/[pid]/stack 看内核态等待对象(如 futex、mutex)
- Java 应用:首选 jstack -l [pid];重点看 BLOCKED (on object monitor) 和 waiting to lock 后的十六进制地址,比对是否构成闭环;注意它不显示 native 层锁或 DB 行锁
从等待链线索反推根本原因
等待链只是表象,每类等待对象对应不同根因方向:
- CriticalSection / Mutex:大概率是代码中 synchronized 或 ReentrantLock 使用顺序不一致,比如线程 A 先锁 X 再锁 Y,线程 B 反过来
- Event / Semaphore:常因信号未触发、计数未归零,或 await/notify 配对缺失,属于逻辑遗漏而非锁竞争
- WaitForMultipleObjects:可能掩盖真实瓶颈,要看 kb 输出最上层调用是否落在业务方法里,否则只是系统级等待
- 数据库连接池耗尽:jstack 会显示大量线程 BLOCKED 在 getConnection(),但等待链里看不到 DB 锁——需同步查 DB 连接数、慢 SQL、事务未提交
不能只靠等待链,还要交叉验证
单靠一次等待链无法定论,必须结合其他信息:
- 检查最近上线的代码变更,特别是加锁范围、锁粒度、资源获取顺序
- 查看 GC 日志是否频繁 Full GC 导致 STW 时间长,被误判为假死
- 用 jstat -gc [pid] 或 Arthas 的 thread -b 快速确认是否有线程在阻塞入口处卡住
- 对 native 层问题,用 perf record -p [pid] 抓热点,看是否卡在 mmap、write 等系统调用

















