jstack是JDK官方轻量级线程快照工具,通过Attach API和JVMTI获取JVM实时线程状态、锁持有及等待关系,支持-F强制附加、-l显示锁详情,能自动检测死锁并定位BLOCKED/WAITING根源,结合top与nid转换可关联OS级线程行为。

Java 线程状态转换和死锁问题的底层排查,核心不在于“看代码”,而在于“抓现场”——即获取并解读 JVM 在某一时刻的真实线程快照。jstack 是最直接、最轻量、无需额外依赖的官方工具,它能穿透 Java 抽象层,暴露线程与锁在 JVM 和 OS 层的真实映射关系。
用 jstack 快速捕获线程运行态
先通过 jps -l 找到目标进程 PID(例如 12345),再执行:
-
jstack 12345:直接输出到终端,适合快速查看 -
jstack -l 12345 > dump.log:加-l参数可显示详细锁信息(包括 owned monitors 和 waiting to lock),这是分析死锁的关键 -
jstack -F 12345:当目标进程无响应、常规 jstack 失败时,强制附加(需 root 或进程同用户权限)
注意:输出中每个线程块以 "Thread-0" #12 prio=5 ... 开头,紧随其后是 java.lang.Thread.State: xxx,这个状态就是 JVM 线程状态(如 RUNNABLE、BLOCKED、WAITING),不是操作系统线程状态,但两者存在映射关系。
对照状态码定位阻塞根源
看到线程状态后,要结合上下文判断真实含义:
立即学习“Java免费学习笔记(深入)”;
-
BLOCKED:正在等待进入 synchronized 块或方法,已明确指出等待哪个 monitor(如
waiting to lock <0x000000076b2222a8>),说明有锁竞争 -
WAITING / TIMED_WAITING:通常调用了
Object.wait()、Thread.join()或LockSupport.park(),需看栈顶方法判断是否合理挂起(比如数据库连接池耗尽导致线程卡在 getConnection()) -
RUNNABLE:不一定真在跑 CPU;Linux 下它可能对应 OS 的 TASK_RUNNING 状态,但若实际在做系统调用(如 socket read),也会显示为 RUNNABLE —— 此时需结合
top -H -p 12345查看哪个 native 线程(nid 转十进制)CPU 高
从输出里识别死锁的两种方式
jstack 对死锁有显式提示,优先看结尾部分:
-
自动检测区块:如果存在死锁,jstack 会在输出末尾明确打印
Found one Java-level deadlock:,并列出互相等待的线程、持有的锁和等待的锁,例如:"Thread-1": waiting to lock 0x000000076b2222a8, which is held by "Thread-2""Thread-2": waiting to lock 0x000000076b2222d8, which is held by "Thread-1" -
手动交叉验证:若没自动提示,就逐个查 BLOCKED 线程:
找到线程 A 的waiting to lock 0x...→ 搜索谁持有该地址的 monitor(找- locked <0x...>)→ 再看那个线程是否也在等另一个锁 → 形成闭环即为死锁
结合操作系统级线索缩小范围
JVM 线程 ID(nid)是十六进制,对应 Linux 的 LWP(轻量级进程)ID:
- 用
top -H -p 12345查看各线程 CPU/内存占用,记下高负载的十六进制 nid(如nid=0x1a2b) - 把
0x1a2b转成十进制(printf "%d\n" 0x1a2b→ 6699),就能在top输出中精准定位该线程 - 再回到 jstack 输出,搜索
nid=0x1a2b,即可把高 CPU 行为和 Java 栈帧关联起来,确认是不是死循环或密集计算
不复杂但容易忽略


















