简体中文
JVM 线上问题排查工具箱:jmap、jstack、jstat 与 Arthas
凌晨两点告警响起:一个服务在抛 OutOfMemoryError,一个接口 30 秒才响应,还有一个 CPU 打满 100%。JDK 自带的工具足够应付这三种场面——本篇按场景而不是按字母序讲它们。
工具地图
| 工具 | 用途 | 使用时机 |
|---|---|---|
jps | 列出 Java 进程 PID | 永远的第一条命令 |
jmap | 内存直方图、堆转储 | OOM、内存泄漏 |
jstack | 线程转储、死锁检测 | 请求卡死、服务假死 |
jstat | GC 统计采样 | 观察回收行为 |
jcmd | 一站式诊断入口(JDK 8+) | 上面所有事 |
| JMX + VisualVM | 远程可视化监控 | 长时间观察 |
| Arthas | 交互式在线诊断 | 不能重启,但急需答案 |
场景一:服务 OOM / 内存缓慢增长
目标:找出是哪些对象吃掉了堆。
最可靠的证据是"死亡瞬间"的堆转储。提前埋好开关:
bash
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dump/OOM 发生时 JVM 自动写出 .hprof 文件。用 Eclipse MAT 或 VisualVM 打开,看支配树(Dominator Tree):通常一两个对象就占了大半堆。真实事故里的经典元凶:
- 拿
HashMap当缓存用、没有淘汰策略的无界缓存; - 线程池里 ThreadLocal 的 List 从不清理;
- 分页查询写成全量加载——"一个小报表"查了 800 万行进内存。
不想下载大转储时,直方图可以快速看每类的实例数和字节数:
bash
jmap -histo <pid> | head -30 # 所有对象
jmap -histo:live <pid> | head -30 # 仅存活对象——会触发一次 Full GC,避开业务高峰排在前列的业务类出现成千上万个实例,通常直接指向问题代码。
场景二:接口卡死,服务假死
目标:弄清线程在等什么。
bash
jstack <pid> > thread-dump.txt在转储里搜 BLOCKED 状态和 Found one Java-level deadlock——JVM 会自动检测经典的锁环并打印涉及的两把锁。典型根因是加锁顺序不一致:方法 A 先锁资源 1 再锁资源 2,方法 B 反过来。修法是全局统一加锁顺序——更好的做法是用 java.util.concurrent 的现成原语替代手写同步。
间隔几秒连抓三次转储:三次都 BLOCKED 在同一把锁上的线程就是卡死的请求;栈在变化的线程只是慢,不是死。
场景三:CPU 飙到 100%
目标:找到烧掉那个核的线程。
bash
top -Hp <pid> # 列出 JVM 内线程,记下最耗 CPU 的线程号
printf '%x\n' <tid> # 转十六进制,例如 0x2d64
# 在 jstack 转储里搜 nid=0x2d64两种常见结局:
- 线程栈显示你的代码在循环——真正的热点循环 Bug;
- 排最前面的是 GC 线程——"CPU 问题"其实是 GC 问题。用
jstat -gcutil <pid> 1000确认:如果FGC持续上涨,接着做 GC 日志分析。
场景四:用 jstat 持续观察 GC
jstat 对 GC 计数器做无侵入采样:
bash
jstat -gcutil <pid> 1000 10 # 每秒采样一次,共 10 次,输出各代占用百分比
jstat -gc <pid> 1000 10 # 绝对值(KB)-gcutil 以百分比显示 Eden / Survivor / 老年代 / 元空间占用,外加 YGC、FGC 及累计耗时。从一小段采样序列就能估算:
- 年轻代填充速率——两次采样间 Eden 涨了多少 → 推 Young GC 频率;
- 平均 Young GC 耗时——
YGCT / YGC; - 晋升压力——每次 Young GC 后老年代涨了多少;
- Full GC 代价——
FGCT / FGC。
这套推算流程,正是 EasyGC 报告从完整 GC 日志里自动完成的事情——还附带图表和泄漏检测。
场景五:远程可视化监控
需要长时间观察时,用 VisualVM 等 JMX 客户端远程连接。目标 JVM 需要:
bash
-Dcom.sun.management.jmxremote.port=8888
-Djava.rmi.server.hostname=<服务器IP>
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=falseWARNING
authenticate=false 会把 JVM 暴露给所有能访问该端口的人。务必绑定内网网卡 + 防火墙限制,永远不要把 JMX 暴露到公网。
VisualVM 的 "Visual GC" 插件能实时渲染各代内存变化——压测时直观"感受"应用的分配模式非常好用。
场景六:不能重启,但急需答案
Arthas(阿里巴巴开源)可以附着到运行中的 JVM 提供交互式命令:dashboard 看全局概况、thread -b 直接定位死锁、jad 反编译确认线上到底跑的是哪个版本、profiler 生成热点火焰图。当生产问题等不来一次重新发布时,它是从症状到根因最快的路。
实用排查顺序
jps→ 找到 PID。jstat -gcutil→ 是 GC 问题吗?(是 → GC 日志分析)jstack×3 → 是锁/延迟问题吗?jmap -histo或堆转储 → 是内存问题吗?- Arthas / JMX → 追问现场细节。
相关文章
- JVM 性能监控指标 — 这些工具采样的数字详解
- JVM 性能优化指南 — 诊断之后改什么
- 开启 GC 日志 — 一切分析的基础