跳到主要内容

JVM 诊断工具

问题

JVM 提供了哪些诊断工具?如何用 jstack/jmap/jstat 排查线上问题?Arthas 有什么功能?

答案

JDK 自带工具一览

工具全称用途
jpsJVM Process Status列出所有 Java 进程
jstatJVM Statistics Monitoring监控 GC、类加载等统计信息
jinfoJVM Configuration Info查看/修改 JVM 参数
jmapJVM Memory Map导出堆转储、查看堆内存
jstackJVM Stack Trace导出线程快照
jcmdJVM Command多功能命令行工具(替代以上多个)
jhsdbHotSpot DebuggerJDK 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/S1Survivor 0/1 使用率(%)
EEden 使用率(%)
OOld 老年代使用率(%)
MMetaspace 使用率(%)
CCS压缩类空间使用率(%)
YGC/YGCTYoung GC 次数/总耗时(秒)
FGC/FGCTFull GC 次数/总耗时(秒)
GCTGC 总耗时

关键指标判断:

指标正常告警
YGC 间隔> 5 秒< 1 秒(对象创建过快)
O(老年代)< 70%> 90% 且持续增长(可能泄漏)
FGC0 或极少频繁增长(需要排查)
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 -dumpjmap -histo:live 会触发 Full GC,在大堆场景下可能导致长时间 STW。生产环境建议:

  1. 配置 -XX:+HeapDumpOnOutOfMemoryError 自动导出
  2. 使用 jcmd <pid> GC.heap_dump heap.hprof 替代(更安全)
  3. 或使用 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
profilerCPU/内存火焰图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线程状态可视化,检测死锁
SamplerCPU/内存采样分析(比 Profiler 轻量)
Profiler详细的方法级性能分析
Heap Dump分析堆转储(功能弱于 MAT)
# JDK 8 自带(jvisualvm)
jvisualvm

# JDK 9+ 需要单独下载
# https://visualvm.github.io/download.html

工具选择指南

场景推荐工具
快速查看进程和 GCjps + jstat
线上 CPU 飙高top -Hp + jstackArthas thread -n 5
线上死锁jstack -lArthas 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,原因:

  1. jcmd 是 Oracle 官方推荐的多功能工具,可替代 jmap、jstack、jinfo
  2. jcmd 的 GC.heap_dump 比 jmap 更安全
  3. jcmd 支持更多诊断命令(VM.native_memory、VM.classloader_stats 等)
  4. 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

相关链接