先记住分工
| 工具 | 回答的问题 | 典型场景 |
|---|---|---|
| jstack | 线程在干什么 | CPU 飙高、死锁、请求卡住 |
| jmap | 堆里装了什么 | 内存泄漏、OOM 前的现场 |
| jstat | 运行时在变什么 | GC 趋势、ClassLoader、编译压力 |
| jcmd | 统一入口 | 新版本里 jstack/jmap 的功能都收编到它了 |
jstack:CPU 飙高的第一站
# 第一步:找出占 CPU 最高的 Java 线程
$ top -Hp $PID # -H 看线程级,按 CPU 排序
# PID USER %CPU ...
# 1234 app 98.5 ...
$ printf "%x
" 1234 # 线程 ID 转十六进制 → 4d2
$ jstack $PID | grep -A 30 "nid=0x4d2"
# "http-nio-8080-exec-3" #23 daemon prio=5 tid=0x... nid=0x4d2 runnable
# at java.util.HashMap.hash(HashMap.java:340)
# at java.util.HashMap.get(HashMap.java:555)
# ...调用栈直指案发方法诀窍是 top -H 找线程 → 转十六进制 → jstack 抓栈,三步锁定"哪个线程在哪个方法里耗 CPU"。jstack 还能检测死锁:jstack -l PID 末尾出现 "Found 1 deadlock" 就是铁证。
jmap:堆转储的两种姿势
排查内存问题靠"堆转储"(heap dump)。被动触发:JVM 加 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/oom.hprof,OOM 时自动 dump;主动触发:jmap -dump:format=b,file=/tmp/heap.hprof PID。注意主动 dump 会触发 Full GC 且全程 STW——生产环境先摘流量再操作,否则业务会明显卡顿。
jstat:分代与 GC 的实时曲线
$ jstat -gcutil PID 1000 5
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 85.42 73.11 45.23 95.2 92.1 124 1.832 0 0.000 1.832
...(每秒一行,输出 5 次后停)
// S0/S1:两个 Survivor 占比(必有一个是 0)
// E:Eden;O:Old;M:Metaspace;CCS:压缩类空间
// YGC/YGCT:Young GC 次数/总耗时;FGC/FGCT:Full GC 同理
// 现场判断:E 接近 100% 即将 Minor GC;O 持续上涨可能要 Full GC
// FGCT/YGCT 飙升意味着 GC 在生消耗,不是偶发事件jcmd:统一入口
JDK 9 起 jstack/jmap 的核心功能都收编到 jcmd。jcmd PID Thread.print 就是 jstack,jcmd PID GC.heap_dump /tmp/h.hprof 就是 jmap,jcmd PID VM.flags 看运行时参数。最实用的是 jcmd PID help,列出该 PID 支持的命令,忘了语法查一下比 Google 快。下一篇用 jmap 拿到的堆转储走进 MAT,看"内存里到底谁占了九成"。
评论 (0)