jstack/jcmd/jstatd 命令
线程与诊断守护进程
上一节介绍了内存相关的命令行工具,这一节来看线程诊断和诊断守护进程。
jstack:线程堆栈分析
jstack 是查看 Java 进程线程堆栈的首选工具,是排查死锁、线程阻塞、CPU 热点等问题的核心武器。
基本用法
bash
jstack [option] <pid>生成线程堆栈
bash
# 查看完整线程堆栈
jstack 67890
# 输出(部分):
# 2026-03-22 10:30:15
# Full thread dump Java HotSpot(TM) 64-Bit Server VM (17.0.2+8-86 mixed mode, sharing):
#
# "main" #1 prio=5 os_prio=31 cpu=15.23ms elapsed=1200.00s tid=0x00007f8a48000800 nid=0x1703 waiting
# java.lang.Thread.State: RUNNABLE
# at java.io.FileInputStream.readBytes(Native Method)
# at java.io.FileInputStream.read(FileInputStream.java:269)
# at Main.main(Main.java:10)
#
# "Reference Handler" #2 daemon prio=10 os_prio=31 cpu=0.78ms elapsed=1200.00s tid=0x00007f8a48100a00 nid=0x4f03 waiting on [0x0000000000000000]
# java.lang.Thread.State: WAITING
# at java.lang.Object.wait(Native Method)
# at java.lang.ref.Reference.waitForReferencePendingList(Reference.java:241)
# ...线程状态解读
每个线程的信息包含:
"thread-name" #<number> [daemon] prio=<priority> os_prio=<os_priority> cpu=<cpu_time> elapsed=<elapsed_time> tid=<thread_id> nid=<native_id> <state>
java.lang.Thread.State: <thread_state>
at <class>.<method>(<source>)关键字段说明:
| 字段 | 含义 |
|---|---|
tid | Java 线程 ID(对象地址) |
nid | Native 线程 ID(对应 OS 线程) |
prio | 线程优先级 |
daemon | 是否守护线程 |
State | 线程状态 |
线程状态(Thread.State)
NEW → 线程创建但未启动
RUNNABLE → 正在执行或等待 CPU
BLOCKED → 等待获取监视器锁
WAITING → 无限期等待(wait/join/park)
TIMED_WAITING → 有限期等待(sleep/join with timeout)查找死锁
bash
# 使用 -l 选项可以检测死锁
jstack -l 67890
# 如果存在死锁,输出中会包含:
# Found one Java-level deadlock:
# ============================
#
# "Thread-1":
# waiting to lock monitor 0x00007f8a4800a5b8 (obj=0x000000076cf8bdf0)
# which is held by "Thread-0"
# "Thread-0":
# waiting to lock monitor 0x00007f8a4800a7d0 (obj=0x000000076cf8bdf8)
# which is held by "Thread-1"死锁输出包含四个关键信息:
1. 死锁线程:Thread-1、Thread-0
2. Thread-1 在等待的锁:0x00007f8a4800a5b8
3. Thread-1 持有的锁:0x00007f8a4800a7d0
4. Thread-0 在等待的锁:0x00007f8a4800a7d0
5. Thread-0 持有的锁:0x00007f8a4800a5b8查找 CPU 占用最高的线程
bash
# 1. 先用 top 找到最耗 CPU 的 Java 线程
top -Hp 67890
# 输出(部分):
# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
# 12345 app 20 0 12.0g 8.0g 52m S 5.2 2.5 10:23.45 java
# 12346 app 20 0 12.0g 8.0g 52m S 3.1 2.5 5:12.33 java
# 12347 app 20 0 12.0g 8.0g 52m S 1.2 2.5 2:10.22 java
# 2. 把最耗 CPU 的线程 ID(12345)转成 16 进制
printf '%x\n' 12345
# 输出:3039
# 3. 用 jstack 查找对应的线程
jstack 67890 | grep -A 20 'nid=0x3039'常见线程模式识别
1. waiting on <monitor>(等待锁)
"pool-1-thread-1" #12 prio=5 os_prio=0 cpu=1234ms elapsed=456s tid=0x... nid=0x... waiting on [0x...]
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at com.example.Resource.get(Resource.java:45)
← locked <0x000000076cf8bdf0>这说明线程在等待其他线程释放锁。
2. waiting for monitor entry(等待进入 synchronized 块)
"pool-1-thread-2" #13 prio=5 os_prio=0 cpu=234ms elapsed=456s tid=0x... nid=0x... waiting for monitor entry [0x...]
java.lang.Thread.State: BLOCKED
at com.example.Lock.acquire(Lock.java:30)
← waiting to lock <0x000000076cf8bdf0>这说明线程在排队等待获取锁,锁被其他线程持有。
3. parking(线程挂起)
"pool-1-thread-1" #12 prio=5 os_prio=0 cpu=1234ms elapsed=456s tid=0x... nid=0x... waiting
java.lang.Thread.State: WAITING
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:864)parking 是 JUC 包的 LockSupport.park() 导致的,通常在线程等待锁或条件变量时出现。
jcmd:多功能诊断工具
jcmd 是 JDK 8+ 引入的统一诊断工具,可以替代多个旧工具。
基本用法
bash
jcmd <pid> <command> [arguments]查看可用命令
bash
# 列出指定进程支持的所有诊断命令
jcmd 67890 help
# 输出:
# The following commands are available:
# JFR.stop
# JFR.start
# JFR.dump
# JFR.check
# VM.native_memory
# VM.check_commercial_features
# VM.unlock_commercial_features
# ManagementAgent.stop
# ManagementAgent.start
# ManagementAgent.list
# GC.rotate_log
# GC.class_histogram
# GC.heap_dump
# GC.run_finalization
# GC.run
# VM.uptime
# VM.flags
# VM.system_properties
# VM.command_line
# VM.version
# help常用命令
1. 生成堆转储
bash
# 等同于 jmap -dump
jcmd 67890 GC.heap_dump /path/to/heap.hprof
# 生成包含所有对象的堆转储(不加 live)
jcmd 67890 GC.heap_dump /path/to/heap_all.hprof
# 推荐:生成只包含存活对象的堆转储
jcmd 67890 GC.heap_dump -all=false /path/to/heap.hprof2. 查看类直方图
bash
# 等同于 jmap -histo
jcmd 67890 GC.class_histogram | head -30
# 按内存占用排序
jcmd 67890 GC.class_histogram | sort -k2 -rn | head -203. 触发 GC
bash
# 触发一次 Full GC
jcmd 67890 GC.run
# 触发 Finalization
jcmd 67890 GC.run_finalization
# 触发 GC 并打印详细信息
jcmd 67890 GC.class_histogram4. 查看 JVM 信息
bash
# 查看启动参数
jcmd 67890 VM.flags
# 查看系统属性
jcmd 67890 VM.system_properties
# 查看 JVM 版本
jcmd 67890 VM.version
# 查看 JVM 运行时间
jcmd 67890 VM.uptime5. 旋转 GC 日志
bash
# 相当于执行了日志 rotation
jcmd 67890 GC.rotate_log
# 当使用 -XX:+UseGCLogFileRotation 参数时有用jcmd 与旧工具对比
| 功能 | jcmd | 旧工具 |
|---|---|---|
| 生成堆转储 | jcmd PID GC.heap_dump | jmap -dump |
| 类直方图 | jcmd PID GC.class_histogram | jmap -histo |
| 触发 GC | jcmd PID GC.run | System.gc() |
| 查看线程 | jcmd PID Thread.print | jstack |
| 查看参数 | jcmd PID VM.flags | jinfo |
jstatd:诊断守护进程
jstatd 是 RMI 服务器,允许远程机器访问本地 JVM 的诊断工具。
为什么需要 jstatd
默认情况下,jps 和 jstat 只能访问本地 JVM。jstatd 通过 RMI 暴露 JVM 信息,使远程机器也能诊断。
本地机器:运行 jstatd(RMI 服务器)
│
│ RMI
▼
远程诊断工具(jps、jstat、jvisualvm)启动 jstatd
bash
# 基本启动
jstatd -J-Djava.security.policy=jstatd.all.policy
# 指定端口
jstatd -p 1999 -J-Djava.security.policy=jstatd.all.policy
# 指定 RMI 注册端口
jstatd -J-Djava.rmi.server.hostname=192.168.1.100 \
-J-Djava.security.policy=jstatd.all.policy安全策略文件
jstatd.all.policy 内容:
grant {
permission java.security.AllPermission;
};远程诊断示例
bash
# 在目标机器启动 jstatd
ssh user@target-server
jstatd -J-Djava.security.policy=jstatd.all.policy
# 从本地机器连接
jps rmi://target-server:1099
jstat -gcutil rmi://target-server:1099 1000VisualVM 远程连接
通过 jstatd,VisualVM 可以连接到远程 JVM:
VisualVM → 添加远程主机 → 输入 IP:port → 自动发现 JVM 进程注意事项
jstatd 安全考虑:
1. 默认没有安全策略,任何人都可以访问
2. 生产环境慎用,或配置严格的防火墙
3. 推荐方案:跳板机 + VPN
替代方案:JMX 远程监控
-XX:+Manageable 标志开启
-Dcom.sun.management.jmxremote命令组合实战
场景:发现应用响应变慢
bash
# 1. 找到进程
jps | grep MyApp
# 输出:67890 com.example.MyApp
# 2. 查看线程堆栈(快速定位)
jstack 67890 > thread_dump_$(date +%Y%m%d_%H%M%S).txt
# 3. 查看 GC 情况
jstat -gcutil 67890 1000 5
# 4. 如果是死锁
jstack -l 67890 | grep -A 20 "deadlock"
# 5. 如果是 CPU 问题
top -Hp 67890
# 找到最高 CPU 的线程 ID,转 16 进制
printf '%x\n' <thread_id>
# 在 thread dump 中搜索
jstack 67890 | grep -B 5 'nid=0x<hex>'本节小结
jstack/jcmd/jstatd 核心要点:
| 命令 | 核心功能 | 关键选项 |
|---|---|---|
| jstack | 线程堆栈分析 | -l 死锁检测,-F 强制抓取 |
| jcmd | 统一诊断入口 | GC.heap_dump、GC.class_histogram |
| jstatd | 远程诊断守护进程 | -p 指定端口 |
jstack 是排查线程问题的首选工具,死锁、阻塞、CPU 热点都能找到线索。
下一节,我们来看 jConsole/VisualVM 使用。
