JVM命令 之 jstack
jstack 是 JDK 自带的用于 打印 Java 进程中所有线程的栈跟踪信息(Thread Dump) 的诊断工具。它是排查 CPU 飙高、死锁、线程阻塞、响应慢 等问题的核心命令。
📌 一、基本语法
jstack [options] <pid>
jstack [options] <executable> <core>
jstack [options] [server_id@]<remote server IP or hostname>
✅ 最常用的是第一种:
jstack <pid>
其中<pid>是通过jps或ps获取的 Java 进程 ID。
🔧 二、选项参数详解
1. -l(小写 L)—— 显示额外的锁信息(强烈推荐)
jstack -l <pid>
- 除了线程栈,还会显示:
- 同步锁(monitor)的持有和等待情况
java.util.concurrent锁(如ReentrantLock,ThreadPoolExecutor内部锁)的详细信息
- 输出中会出现:
- parking to wait for <0x000000076b8c3450> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
✅ 用途:精准定位死锁、线程等待原因。
💡 建议:除非性能极度敏感,否则 always use
-l。
2. -F(Force)—— 强制打印(当进程无响应时)
jstack -F <pid>
- 当目标 JVM 卡死、无法响应 Attach 请求 时使用
- 绕过正常的 socket 通信,改用 操作系统级 ptrace 机制 强制读取内存
- 前提:
- 当前用户有
ptrace权限(Linux 上通常需要root或相同用户) - 系统未禁用
ptrace(如 Kubernetes 默认允许,但某些安全策略会限制)
- 当前用户有
⚠️ 注意:
-F模式下 不会输出-l的锁信息- 可能因权限或内核限制失败(尤其在容器、gVisor、Kata 等沙箱环境)
✅ 典型场景:
# 正常 jstack 失败
jstack 12345
# 报错: Unable to open socket file...
# 尝试强制
jstack -F 12345
3. -m —— 混合模式(打印 native + Java 栈)【已废弃/不推荐】
jstack -m <pid>
- 试图同时打印 Java 方法栈 + native (C/C++) 方法栈
- 但在现代 JDK(JDK 8+)中基本无效,因为:
- HotSpot 默认不保留完整的 native 栈符号
- 需要 debug 版本的 JVM 或特殊编译选项
- 输出中 native 帧通常显示为
[0x00007f...]地址,无实际意义
❌ 结论:不要使用 -m,改用 perf + async-profiler 分析 native 栈
4. -h / -help —— 显示帮助
jstack -help
输出简要用法说明。
🚫 三、不支持的组合
| 组合 | 结果 |
|---|---|
-F -l |
❌ 无效。-F 模式不支持锁信息 |
-F -m |
❌ 无效。-F 不支持 native 栈 |
-l -m |
⚠️ 可执行,但 -m 无实际作用 |
✅ 正确用法只有两种:
jstack -l <pid>→ 正常情况,带锁信息jstack -F <pid>→ 进程无响应时,强制抓取(无锁信息)
📋 四、输出格式解析(关键字段)
一个典型的线程 dump 片段:
"Thread-0" #12 prio=5 os_prio=0 tid=0x00007f8a1c000000 nid=0x303e runnable [0x00007f8a0dffe000]
java.lang.Thread.State: RUNNABLE
at com.example.App.doWork(App.java:42)
at com.example.App.lambda$main$0(App.java:28)
- locked <0x000000076b8c3450> (a java.lang.Object)
- waiting to lock <0x000000076b8c3460> (a java.lang.Object) owned by "Thread-1" #13
| 字段 | 含义 |
|---|---|
"Thread-0" |
线程名(可通过 Thread.setName() 设置) |
#12 |
JVM 内部线程 ID |
prio=5 |
Java 优先级(1~10) |
os_prio=0 |
操作系统优先级 |
tid=0x... |
线程在 JVM 中的地址 |
nid=0x303e |
Native Thread ID(十六进制) → 用于与 top -H 对应 |
runnable |
线程状态(runnable, waiting, timed_waiting, blocked) |
java.lang.Thread.State |
Java 层面的线程状态 |
- locked ... |
当前持有的锁 |
- waiting to lock ... owned by "Thread-1" |
正在等待的锁及持有者 → 死锁线索! |
🛠 五、实战使用技巧
✅ 1. 定位高 CPU 线程(经典流程)
# Step 1: 找到 Java PID
jps -l
# Step 2: 查看各线程 CPU 使用(按线程)
top -H -p <PID>
# Step 3: 找到高 CPU 线程 ID(十进制),转为十六进制
printf "%x\n" <thread_id> # 如 12350 → 303e
# Step 4: 抓取线程栈
jstack -l <PID> > threaddump.txt
# Step 5: 在 dump 中搜索 nid=0x303e
grep -A 30 "nid=0x303e" threaddump.txt
✅ 2. 检测死锁
jstack -l <PID>
如果存在死锁,开头会明确提示:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f8a1c000000 (object 0x000000076b8c3450, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f8a1c000001 (object 0x000000076b8c3460, a java.lang.Object),
which is held by "Thread-1"
✅ 3. 容器中替代方案(当 jstack 失败时)
# 方案 1: 使用 kill -3(最可靠!)
kill -3 <PID>
# 线程栈会输出到应用 stdout,在容器中可通过 kubectl logs 查看
# 方案 2: 使用 jcmd(更现代)
jcmd <PID> Thread.print -l
💡
kill -3不依赖 Attach 机制,100% 有效!
✅ 4. 连续抓取(分析间歇性问题)
# 每 5 秒抓一次,共 3 次
for i in {1..3}; do
jstack -l <PID> >> /tmp/threaddump.$(date +%s).txt
sleep 5
done
对比多次 dump,可发现 持续处于 RUNNABLE 的线程(可能是死循环)。
⚠️ 六、常见错误与解决
❌ 错误 1:
Unable to open socket file: target process not responding or HotSpot VM not loaded
- 原因:Attach 机制失败(/tmp 不可写、Alpine 镜像、进程卡死等)
- 解决:
- 用
jstack -F - 或改用
kill -3 <PID> - 或用
jcmd <PID> Thread.print
- 用
❌ 错误 2:
Not enough space for virtual memory
- 原因:容器内存不足,无法分配 dump 缓冲区
- 解决:先扩容容器内存,或直接用
kill -3
❌ 错误 3:无输出或输出不全
- 原因:重定向时未加
-l,或进程退出太快 - 解决:确保进程存活,使用
jstack -l > file
📊 七、jstack vs jcmd Thread.print
| 特性 | jstack |
jcmd <pid> Thread.print |
|---|---|---|
| 是否需要 Attach 文件 | ✅ 是 | ✅ 是 |
支持 -l 锁信息 |
✅ 是 | ✅ 加 -l 参数 |
| 是否被官方推荐 | ⚠️ 旧工具 | ✅ JDK 7+ 推荐 |
| 功能扩展性 | ❌ 固定 | ✅ 可与其他 jcmd 命令统一管理 |
| 容器兼容性 | 较差 | 略好 |
✅ 建议:新项目优先使用
jcmd,但jstack仍广泛兼容。
✅ 八、最佳实践总结
- 默认加
-l:jstack -l <pid>获取完整锁信息 - CPU 高时配合
top -H+nid定位 - 容器中首选
kill -3 <pid>,避免 Attach 问题 - 死锁问题看 dump 开头是否有 "Found one Java-level deadlock"
- 不要用
-m,native 栈分析请用async-profiler
🔚 附:完整命令速查
# 基础线程 dump(带锁)
jstack -l 12345 > threaddump.log
# 强制 dump(进程无响应)
jstack -F 12345 > threaddump_force.log
# 现代替代(推荐)
jcmd 12345 Thread.print -l > threaddump_jcmd.log
# 最可靠(容器友好)
kill -3 12345 # 日志在 stdout
掌握 jstack,你就拥有了透视 Java 线程行为的“X光机”!
本文来自博客园,作者:蓝迷梦,转载请注明原文链接:https://www.cnblogs.com/hewei-blogs/articles/19447171

浙公网安备 33010602011771号