JVM 统一诊断工具 jstack 使用
jstack 深度实战指南
jstack (Stack Trace for Java) 用于生成虚拟机当前时刻的线程快照(Thread Dump)。它是定位线程长时间停顿、CPU 占用过高、死锁、无法响应请求等问题的“听诊器”。
1. 命令参数详解
基本语法:jstack [options] <PID>
| 参数 | 示例 | 作用 |
|---|---|---|
| 无参数 | jstack 1234 |
最常用。输出标准线程堆栈。 |
| -l | jstack -l 1234 |
Long listing。除堆栈外,显示关于锁的附加信息(如 Ownable Synchronizers,即 ReentrantLock 等 JUC 锁)。建议默认加上。 |
| -F | jstack -F 1234 |
Force。当进程无响应(hung 住)时强制打印堆栈。注意:此操作可能会让进程暂停更久,谨慎使用。 |
| 重定向 | jstack 1234 > dump.log |
将输出保存到文件,便于离线分析或发送给同事。 |
2. 读懂线程堆栈日志(解剖图)
一行标准的线程日志包含丰富的信息,我们需要学会“拆解”它。
日志示例:
Plaintext
"http-nio-8080-exec-1" #12 daemon prio=5 os_prio=0 tid=0x00007f... nid=0x1a2b waiting on condition [0x00007f...]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
...
字段逐一解析:
- Thread Name (
"http-nio-8080-exec-1"): 线程名称。最佳实践: 在代码中给线程池自定义名称(如order-process-thread),排查问题时能一眼看出业务归属。 - daemon: 标记是否为守护线程。
- prio=5: Java 代码层面的优先级。
- tid: Java 内存中的线程 ID。
- nid (
0x1a2b): 关键字段! Native Thread ID,即操作系统层面的线程 ID(16进制)。- 核心用途:用于将 Linux 的
top -Hp显示的 PID(10进制)与这里的堆栈对应起来。
- 核心用途:用于将 Linux 的
- Status: 线程的大致状态。
- java.lang.Thread.State: 具体的 Java 线程状态(详见下文)。
3. 核心线程状态 (Thread State) 详解
这是分析问题的核心,必须理解这几种状态的区别:
| 状态 | 含义 | 风险等级 | 排查思路 |
|---|---|---|---|
| RUNNABLE | 正在运行,或者在等待 CPU 时间片,或者在进行系统调用(如网络读取)。 | ⭐️ / ⭐️⭐️⭐️ | 如果大量线程是 RUNNABLE 且代码位置相同,通常是性能瓶颈或死循环。 |
| BLOCKED | 被阻塞。正在等待获取 synchronized 锁。 | ⭐️⭐️⭐️⭐️⭐️ | 高危。说明并发竞争非常激烈。如果长时间不释放,会导致雪崩。 |
| WAITING | 无限期等待。调用了 wait()、join() 或 LockSupport.park()。 |
⭐️⭐️ | 需要其他线程来唤醒(notify/unpark)。如果是线程池空闲线程,则正常。 |
| TIMED_WAITING | 限时等待。调用了 sleep(t) 或 wait(t)。 |
⭐️ | 通常正常。但如果大量线程都在 sleep,响应也会变慢。 |
4. 经典场景实战分析
场景一:CPU 飙升 100% (定位高耗时代码)
这是 jstack 最典型的用法,结合 Linux 命令使用。
-
定位进程:
top(假设 PID 为 10997)。 -
定位线程:
top -Hp 10997。观察哪个线程%CPU最高(假设是 11005)。 -
换算进制: 将 11005 转为 16 进制。
Bash
printf "%x\n" 11005 # 输出 2aff -
精准查找:
Bash
jstack 10997 | grep -A 20 "2aff"结果解读:
你看到的堆栈顶层代码,就是正在疯狂消耗 CPU 的逻辑。常见的有:复杂计算、死循环、大量字符串拼接、频繁 GC(堆栈会显示 GC 线程)等。
场景二:死锁 (Deadlock)
当两个线程互相持有对方需要的锁时,就会死锁。jstack 会自动检测并打印在日志末尾。
日志特征:
搜索关键字 Found one Java-level deadlock。
Plaintext
Found one Java-level deadlock:
=============================
"Thread-A":
waiting to lock monitor 0x000000005... (object 0x000000076...)
locked 0x000000075...
"Thread-B":
waiting to lock monitor 0x000000075... (object 0x000000075...)
locked 0x000000076...
解决:根据堆栈显示的类名和行号,优化加锁顺序或使用 tryLock。
场景三:应用无响应,但 CPU 很低 (外部资源卡顿)
应用突然不处理请求了,但是 CPU 使用率几乎为 0。这通常是线程都在等待外部资源。
分析步骤:
-
导出堆栈:
jstack <PID> > dump.log -
统计线程状态:
Bash
grep "java.lang.Thread.State" dump.log | sort | uniq -c -
如果发现大量 RUNNABLE 状态,但 CPU 很低,检查堆栈内容,是否卡在 Socket 读取上:
Plaintext
at java.net.SocketInputStream.socketRead0(Native Method)结论:这说明数据库查询极慢、Redis 卡顿或调用的第三方 HTTP 接口没有响应,导致线程池被耗尽。
场景四:大量 BLOCKED (锁竞争)
如果发现大量线程状态为 BLOCKED (on object monitor),且都在等待同一个对象地址。
Plaintext
"Thread-5" prio=5 tid=0x01... nid=0x2... blocked
- waiting to lock <0x0000000780a000b0> (a java.lang.Object)
- waiting for monitor entry [0x00000000...]
分析:找到持有锁 <0x0000000780a000b0> 的线程(搜索 locked <0x0000000780a000b0>),查看它为什么执行这么慢(可能在做 IO 或复杂计算)。
5. 高级技巧与注意事项
-
多次采样 (重要)
一次 dump 只是瞬间快照。如果怀疑某个线程卡死,建议间隔 5 秒执行 3 次 jstack。
- 如果 3 次结果中,该线程一直停在同一行代码,说明真的卡死了。
- 如果一直在变动,说明只是执行慢,但还在跑。
-
现代替代品: jcmd
在 JDK 8 以后,官方推荐使用 jcmd,功能更全。
jcmd <PID> Thread.print -
在线分析工具
在线可视化工具:
- FastThread.io (业界最强,推荐)
- JProfiler / VisualVM (本地 GUI 工具)

浙公网安备 33010602011771号