外观
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 → 对象过早晋升 |
E | Eden 使用率 | 正常锯齿波动 |
O | 老年代使用率 | 持续上涨不回落 = 内存泄漏 |
M | 元空间使用率 | 接近 100 且 FGC 后不降 → 类泄漏 |
YGC/YGCT | Young GC 次数 / 累计耗时 | 平均单次 = YGCT/YGC,>50ms 偏慢 |
FGC/FGCT | Full 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 文件在容器文件系统里。