Skip to content

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>)

关键字段说明:

字段含义
tidJava 线程 ID(对象地址)
nidNative 线程 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.hprof

2. 查看类直方图

bash
# 等同于 jmap -histo
jcmd 67890 GC.class_histogram | head -30

# 按内存占用排序
jcmd 67890 GC.class_histogram | sort -k2 -rn | head -20

3. 触发 GC

bash
# 触发一次 Full GC
jcmd 67890 GC.run

# 触发 Finalization
jcmd 67890 GC.run_finalization

# 触发 GC 并打印详细信息
jcmd 67890 GC.class_histogram

4. 查看 JVM 信息

bash
# 查看启动参数
jcmd 67890 VM.flags

# 查看系统属性
jcmd 67890 VM.system_properties

# 查看 JVM 版本
jcmd 67890 VM.version

# 查看 JVM 运行时间
jcmd 67890 VM.uptime

5. 旋转 GC 日志

bash
# 相当于执行了日志 rotation
jcmd 67890 GC.rotate_log

# 当使用 -XX:+UseGCLogFileRotation 参数时有用

jcmd 与旧工具对比

功能jcmd旧工具
生成堆转储jcmd PID GC.heap_dumpjmap -dump
类直方图jcmd PID GC.class_histogramjmap -histo
触发 GCjcmd PID GC.runSystem.gc()
查看线程jcmd PID Thread.printjstack
查看参数jcmd PID VM.flagsjinfo

jstatd:诊断守护进程

jstatd 是 RMI 服务器,允许远程机器访问本地 JVM 的诊断工具。

为什么需要 jstatd

默认情况下,jpsjstat 只能访问本地 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 1000

VisualVM 远程连接

通过 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_dumpGC.class_histogram
jstatd远程诊断守护进程-p 指定端口

jstack 是排查线程问题的首选工具,死锁、阻塞、CPU 热点都能找到线索。

下一节,我们来看 jConsole/VisualVM 使用

基于 VitePress 构建