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> 是通过 jpsps 获取的 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 无实际作用

正确用法只有两种

  1. jstack -l <pid> → 正常情况,带锁信息
  2. 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 仍广泛兼容。


✅ 八、最佳实践总结

  1. 默认加 -ljstack -l <pid> 获取完整锁信息
  2. CPU 高时配合 top -H + nid 定位
  3. 容器中首选 kill -3 <pid>,避免 Attach 问题
  4. 死锁问题看 dump 开头是否有 "Found one Java-level deadlock"
  5. 不要用 -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光机”!

posted @ 2026-01-06 13:23  蓝迷梦  阅读(163)  评论(0)    收藏  举报