JVM 诊断工具
问题
JVM 提供了哪些诊断工具?如何用 jstack/jmap/jstat 排查线上问题?Arthas 有什么功能?
答案
JDK 自带工具一览
| 工具 | 全称 | 用途 |
|---|---|---|
jps | JVM Process Status | 列出所有 Java 进程 |
jstat | JVM Statistics Monitoring | 监控 GC、类加载等统计信息 |
jinfo | JVM Configuration Info | 查看/修改 JVM 参数 |
jmap | JVM Memory Map | 导出堆转储、查看堆内存 |
jstack | JVM Stack Trace | 导出线程快照 |
jcmd | JVM Command | 多功能命令行工具(替代以上多个) |
jhsdb | HotSpot Debugger | JDK 9+ 调试工具 |
jps — 查看 Java 进程
# 列出所有 Java 进程
jps
# 输出: 12345 Application
# 12346 Jps
# 显示完整的主类名和 JVM 参数
jps -lvm
# 输出: 12345 com.example.Application -Xms512m -Xmx1024m
jstat — 监控 GC 统计
最常用的实时监控命令:
# 每隔 1 秒打印一次 GC 统计,共打印 10 次
jstat -gcutil <pid> 1000 10
输出示例及各列含义:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 45.23 67.89 34.56 95.12 91.45 120 1.234 3 0.567 1.801
| 列 | 含义 |
|---|---|
| S0/S1 | Survivor 0/1 使用率(%) |
| E | Eden 使用率(%) |
| O | Old 老年代使用率(%) |
| M | Metaspace 使用率(%) |
| CCS | 压缩类空间使用率(%) |
| YGC/YGCT | Young GC 次数/总耗时(秒) |
| FGC/FGCT | Full GC 次数/总耗时(秒) |
| GCT | GC 总耗时 |
关键指标判断:
| 指标 | 正常 | 告警 |
|---|---|---|
| YGC 间隔 | > 5 秒 | < 1 秒(对象创建过快) |
| O(老年代) | < 70% | > 90% 且持续增长(可能泄漏) |
| FGC | 0 或极少 | 频繁增长(需要排查) |
| FGCT 单次 | < 1 秒 | > 3 秒(停顿过长) |
# 其他常用子命令
jstat -gc <pid> 1000 # 详细的各区域容量和使用量(KB)
jstat -gccapacity <pid> # 各分代最小/最大/当前容量
jstat -gcnew <pid> # 新生代详情
jstat -gcold <pid> # 老年代详情
jstat -gcmetacapacity <pid> # 元空间容量
jstat -class <pid> # 类加载统计
jmap — 堆内存分析
# 查看堆内存概况
jmap -heap <pid>
# 查看对象直方图(按类统计对象数量和大小)
jmap -histo <pid> | head -30
# 输出:
# num #instances #bytes class name
# 1: 5000000 120000000 [B (byte数组)
# 2: 3000000 72000000 java.lang.String
# 3: 1000000 48000000 com.example.UserDTO
# 只统计存活对象(会触发一次 Full GC)
jmap -histo:live <pid> | head -30
# 导出堆转储文件(生产环境谨慎,可能导致长时间 STW)
jmap -dump:live,format=b,file=heap.hprof <pid>
生产环境使用注意
jmap -dump 和 jmap -histo:live 会触发 Full GC,在大堆场景下可能导致长时间 STW。生产环境建议:
- 配置
-XX:+HeapDumpOnOutOfMemoryError自动导出 - 使用
jcmd <pid> GC.heap_dump heap.hprof替代(更安全) - 或使用 Arthas 的
heapdump命令
jstack — 线程分析
# 导出线程快照
jstack <pid> > thread_dump.txt
# 检测死锁
jstack -l <pid>
线程快照分析要点:
"http-nio-8080-exec-1" #25 daemon prio=5 os_prio=0
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.Service.process(Service.java:42)
- waiting to lock <0x000000076ab00f58> (a java.lang.Object)
- locked <0x000000076ab01068> (a java.lang.Object)
at com.example.Controller.handle(Controller.java:28)
| 线程状态 | 含义 | 关注点 |
|---|---|---|
RUNNABLE | 正在执行或等待 CPU | 大量 RUNNABLE 在同一方法 → CPU 热点 |
BLOCKED | 等待获取锁 | 大量 BLOCKED → 锁竞争问题 |
WAITING | 无限等待(wait/join/park) | 可能死锁 |
TIMED_WAITING | 有限等待(sleep/parkNanos) | 正常情况居多 |
排查 CPU 飙高:
# 1. 找到 CPU 最高的进程
top -c
# 2. 找到 CPU 最高的线程
top -Hp <pid>
# 假设线程 ID 为 12345
# 3. 转为十六进制
printf "%x\n" 12345
# 输出: 3039
# 4. 在 jstack 输出中搜索该线程
jstack <pid> | grep "3039" -A 20
# 找到对应线程的堆栈信息
排查死锁:
jstack <pid> | grep -A 5 "deadlock"
jstack 会在末尾自动检测并报告死锁信息:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x... (object 0x..., a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x... (object 0x..., a java.lang.Object),
which is held by "Thread-1"
jcmd — 多功能命令
jcmd 是 JDK 7+ 提供的多功能工具,可以替代 jmap、jstack 等:
# 列出所有 Java 进程
jcmd -l
# 查看可用命令
jcmd <pid> help
# 导出堆转储
jcmd <pid> GC.heap_dump /path/to/heap.hprof
# 查看线程快照
jcmd <pid> Thread.print
# 查看 JVM 参数
jcmd <pid> VM.flags
# 查看系统属性
jcmd <pid> VM.system_properties
# 查看类加载器统计
jcmd <pid> VM.classloader_stats
# 查看本地内存使用(需要开启 NativeMemoryTracking)
jcmd <pid> VM.native_memory detail
# 触发 GC
jcmd <pid> GC.run
jinfo — 查看/修改参数
# 查看所有 JVM 参数
jinfo -flags <pid>
# 查看特定参数
jinfo -flag MaxHeapSize <pid>
jinfo -flag UseG1GC <pid>
# 动态修改可变参数(部分参数支持)
jinfo -flag +PrintGCDetails <pid> # 开启 GC 详细日志
jinfo -flag -PrintGCDetails <pid> # 关闭
Arthas(阿里开源诊断工具)
Arthas 是阿里开源的 Java 在线诊断工具,功能远超 JDK 自带工具:
# 安装并启动
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 选择要 attach 的 Java 进程
核心命令:
| 命令 | 用途 | 示例 |
|---|---|---|
dashboard | 实时监控(线程、内存、GC) | dashboard |
thread | 线程信息 | thread -n 3(CPU 最高的 3 个线程) |
thread -b | 查找阻塞线程 | thread -b |
jad | 反编译类 | jad com.example.Service |
watch | 方法执行数据观测 | watch com.example.Service process '{params, returnObj}' |
trace | 方法调用链路耗时 | trace com.example.Service process |
stack | 查看方法调用栈 | stack com.example.Service process |
monitor | 方法执行监控 | monitor com.example.Service process -c 5 |
heapdump | 导出堆转储 | heapdump /tmp/heap.hprof |
vmtool | 查看内存中的对象 | vmtool --action getInstances --className com.example.Service |
profiler | CPU/内存火焰图 | profiler start / profiler stop |
sc | 查看类信息 | sc -d com.example.Service |
sm | 查看方法信息 | sm com.example.Service |
ognl | 执行 OGNL 表达式 | ognl '@com.example.Config@timeout' |
典型排查场景:
# 场景1:CPU 飙高定位
thread -n 5 # 查看 CPU 占用最高的 5 个线程及堆栈
# 场景2:方法耗时分析
trace com.example.OrderService createOrder '#cost > 200'
# 输出调用链路中耗时 > 200ms 的方法
# 场景3:查看方法入参和返回值
watch com.example.Service process '{params, returnObj, throwExp}' -x 2
# -x 2 表示展开深度为 2
# 场景4:线上热修复(谨慎使用)
# 1. 反编译当前类
jad --source-only com.example.Service > /tmp/Service.java
# 2. 修改代码
# 3. 编译
mc /tmp/Service.java -d /tmp
# 4. 重新加载
retransform /tmp/com/example/Service.class
VisualVM
VisualVM 是 GUI 监控工具,适合开发环境使用:
| 功能 | 说明 |
|---|---|
| Monitor | 实时查看 CPU、内存、线程、类加载 |
| Threads | 线程状态可视化,检测死锁 |
| Sampler | CPU/内存采样分析(比 Profiler 轻量) |
| Profiler | 详细的方法级性能分析 |
| Heap Dump | 分析堆转储(功能弱于 MAT) |
# JDK 8 自带(jvisualvm)
jvisualvm
# JDK 9+ 需要单独下载
# https://visualvm.github.io/download.html
工具选择指南
| 场景 | 推荐工具 |
|---|---|
| 快速查看进程和 GC | jps + jstat |
| 线上 CPU 飙高 | top -Hp + jstack 或 Arthas thread -n 5 |
| 线上死锁 | jstack -l 或 Arthas thread -b |
| 内存泄漏分析 | jmap -dump + MAT |
| 方法调用追踪 | Arthas trace / watch |
| 长期性能监控 | Prometheus + Grafana |
| 开发环境调试 | VisualVM |
常见面试问题
Q1: 说一下常用的 JVM 诊断工具?
答案:
JDK 自带工具:
- jps:查看 Java 进程
- jstat:监控 GC 和内存统计(最常用的实时监控)
- jmap:导出堆转储和对象直方图
- jstack:导出线程快照,排查死锁和 CPU 热点
- jcmd:多功能命令,可替代以上工具
第三方工具:
- Arthas:阿里开源,功能最强大的在线诊断工具
- MAT:堆转储分析的标准工具
- VisualVM:GUI 监控,适合开发环境
Q2: 线上 CPU 100% 怎么排查?
答案:
# 步骤1: 找到 CPU 最高的 Java 进程
top -c # 记下 PID,假设为 1234
# 步骤2: 找到进程中 CPU 最高的线程
top -Hp 1234 # 记下线程 TID,假设为 5678
# 步骤3: 线程 ID 转十六进制
printf "%x\n" 5678 # 输出 162e
# 步骤4: 在 jstack 中查找该线程
jstack 1234 | grep "162e" -A 30 # 查看堆栈信息
或者用 Arthas 一步到位:thread -n 3 直接显示 CPU 最高的 3 个线程堆栈。
常见原因:死循环、正则表达式回溯、GC 频繁、加密/哈希计算。
Q3: 如何排查线程死锁?
答案:
# 方式1:jstack 自动检测
jstack -l <pid> | grep -A 20 "deadlock"
# 方式2:Arthas
thread -b # 查找阻塞线程
# 方式3:VisualVM → Threads 标签页 → "Detect Deadlock" 按钮
jstack 会在输出末尾报告检测到的死锁信息,包括涉及的线程和锁对象。
Q4: jmap 和 jcmd 哪个更推荐?
答案:
推荐 jcmd,原因:
- jcmd 是 Oracle 官方推荐的多功能工具,可替代 jmap、jstack、jinfo
- jcmd 的
GC.heap_dump比 jmap 更安全 - jcmd 支持更多诊断命令(VM.native_memory、VM.classloader_stats 等)
- jmap 在 JDK 9+ 中的部分功能需要通过 jhsdb 才能使用
Q5: Arthas 的 trace 和 watch 有什么区别?
答案:
| 命令 | 用途 | 输出内容 |
|---|---|---|
trace | 调用链路耗时分析 | 显示方法内部每个子调用的耗时,帮助定位慢在哪一步 |
watch | 方法数据观测 | 显示方法的入参、返回值、异常信息 |
trace 主要回答"哪里慢了",watch 主要回答"方法收到了什么参数、返回了什么"。
Q6: 如何在不重启服务的情况下开启 GC 日志?
答案:
# JDK 8
jinfo -flag +PrintGCDetails <pid>
jinfo -flag +PrintGCDateStamps <pid>
# JDK 9+
jcmd <pid> VM.log what=gc*=info decorators=time,uptime,level output=file=/tmp/gc.log
# Arthas
logger --name com.sun.management -l DEBUG