Skip to content

JDK 命令行工具:jps / jstat / jmap / jstack / jcmd / jinfo ​

六个工具职责重叠很多,但只有一个能安全地在生产上随手用。先记这张表:

工具干什么会不会影响线上
jps列出 Java 进程 pid无
jstat看 GC 统计(采样)无,可长期挂
jinfo看/改 JVM 参数读无;-flag +X 改可管理参数时可能触发 safepoint
jcmd万能入口(推荐)取决于子命令
jstack线程栈会短暂 STW,一般 <100ms
jmap堆直方图 / dump-dump 会长 STW,大堆几十秒

一、jps:先找到进程 ​

bash
jps -lvm

输出:

text
21874 com.bfcube.order.OrderApplication -Xms4g -Xmx4g -XX:MaxGCPauseMillis=200
22155 sun.tools.jps.Jps -lvm
21990 /opt/app/mq-consumer.jar

解释:-l 显示主类或 jar 全路径(用来区分同名进程),-v 显示 JVM 参数——这一列能替代你去翻启动脚本。加 -m 还能看到传给 main 的参数。

jps 看不到进程时

用 ps -ef | grep [j]ava 兜底。jps 只列同一个用户启动的进程,root 起的进程用普通用户 jps 是看不到的(这是最常见的"明明在跑却查不到")。

二、jstat:GC 是否健康的体检仪 ​

bash
jstat -gcutil 21874 1000 10

输出:

text
  S0     S1     E      O      M     CCS    YGC     YGCT     FGC    FGCT     GCT
  0.00  87.34  62.18  71.05  95.22  92.31   1842    12.445    37   18.902   31.347
  0.00  87.34  71.02  71.05  95.22  92.31   1842    12.445    37   18.902   31.347
  0.00  92.11  18.44  74.90  95.22  92.31   1843    12.451    37   18.902   31.353

解释每一列:

列含义危险信号
S0/S1两个 Survivor 区使用率长期接近 100 → 对象过早晋升
EEden 使用率正常锯齿波动
O老年代使用率持续上涨不回落 = 内存泄漏
M元空间使用率接近 100 且 FGC 后不降 → 类泄漏
YGC/YGCTYoung GC 次数 / 累计耗时平均单次 = YGCT/YGC,>50ms 偏慢
FGC/FGCTFull GC 次数 / 累计耗时FGC 持续增长是最需要盯的数

判读口诀:FGC 每分钟都在涨、涨完 O 还下不来 → 内存问题;FGC 很少但 YGC 极频繁 → 堆太小或分配速率太高。

bash
jstat -gccause 21874 1000 5     # 直接看上一次和下一次 GC 的触发原因

输出:

text
  S0     S1     E      O      M     CCS    YGC     FGC    LGCC      GCC
  0.00  92.11  18.44  74.90  95.22  92.31   1843     37   G1 Evacuation Pause   -

解释:LGCC 是上次 GC 原因。看到 Metadata GC Threshold 就是元空间不够;Last GC due to allocation failure 是晋升失败;G1 Humongous Allocation 说明有大对象(超过 region 一半)在直入老年代。

三、jcmd:一个命令顶三个 ​

bash
jcmd 21874 VM.flags                    # 生效参数
jcmd 21874 VM uptime                   # 启动时长
jcmd 21874 GC.heap_info                # 堆各区现状
jcmd 21874 Thread.print > /tmp/t.txt   # 等价 jstack
jcmd 21874 GC.class_histogram | head -20
jcmd 21874 GC.heap_dump /tmp/heap.hprof
jcmd 21874 help                        # 列出该进程支持的全部子命令

GC.heap_info 输出:

text
 num     #instances         #bytes  memory address's   class name
----------------------------------------------
  1:        1284402      143852224  [B (java.base@17.0.11)
  2:         988231       63246784  java.lang.String
  3:         412003       46144336  com.bfcube.model.OrderDetail

解释:jcmd 是官方推荐的统一入口,功能覆盖 jmap/jstack 且参数更规范。优先养成用 jcmd 的习惯,jmap -dump 只在老 JDK 上作为兜底。

四、jstack:抓线程栈的正确姿势 ​

bash
for i in 1 2 3; do jstack -l 21874 > /tmp/stack.$(date +%s).$i.txt; sleep 5; done

输出片段:

text
"http-nio-8080-exec-37" #141 daemon prio=5 os_prio=0 cpu=1842213.44ms elapsed=892143.22s tid=0x00007f3c tid=0x... nid=0x5a2c runnable
   java.lang.Thread.State: RUNNABLE
	at java.net.SocketInputStream.socketRead0(Native Method)
	at com.mysql.cj.protocol.a.NativeProtocol.readMessage(NativeProtocol.java:577)
	at com.bfcube.dao.OrderDao.selectById(OrderDao.java:88)

解释:

  • 连抓 3 次间隔 5 秒是重点。单次快照只能看到"此刻",同一线程 3 次都卡在同一行才叫真卡住。
  • nid=0x5a2c 是内核线程 ID 的十六进制,和 top -H 里的 tid 对应,这是"哪个线程在烧 CPU"的关键桥梁。
  • cpu=1842213.44ms 是该线程累计 CPU 时间,直接排序就能找到热点线程。
  • 上面这个栈看着是 RUNNABLE,实际是卡在 MySQL 网络读——socketRead0 在 Java 里就是 RUNNABLE 状态,别被误导。

jstack 卡住不动

如果目标进程正在长 STW 或已死锁僵住,jstack 会挂住不返回。此时用 jstack -F(强制,走 SA 机制,可能损坏进程)或先 kill -3 <pid> 让 JVM 把栈打到标准输出(日志文件里)。

五、jinfo:看参数、偶尔改参数 ​

bash
jinfo -flag MaxHeapSize 21874
jinfo -flags 21874
jinfo -flag +HeapDumpOnOutOfMemoryError 21874     # 打开可管理标志
jinfo -flag NewRatio=2 21874

输出:

text
-XX:MaxHeapSize=4294967296
Attaching to process ID 21874, please wait...
Debugger attached successfully.

解释:只有标了 manageable 的参数能运行时改,改完立刻生效但不会写回启动脚本——下次重启就没了,属于临时验证手段。

六、组合套路:CPU 高时的三分钟流程 ​

bash
top -c                                   # 1. 找到吃 CPU 的 java pid
top -Hp <pid>                            # 2. 看该进程内哪些线程占 CPU
printf '%x\n' <tid>                      # 3. 转十六进制
jstack <pid> | grep -A 20 'nid=0x<hex>'  # 4. 定位到具体代码行

解释:这四步是 Java 排障最经典的一条链路,值得背下来。完整案例见 CPU 100% 定位。

七、跨机器 / 权限问题 ​

bash
# 报错:Unable to open socket file / well-known file not found
sudo -u appuser jcmd 21874 VM.flags

解释:JDK 工具通过进程内的 attach 机制(/tmp/.java_pid<pid>)通信,要求执行者与目标进程同用户。root 直接跑经常失败,正确做法是 sudo -u <启动用户>。容器里还要 kubectl exec 进容器内执行,因为 attach 文件在容器文件系统里。