JVM 监控与诊断
JVM监控与诊断是保障Java应用稳定运行的重要手段。通过监控JVM的运行状态,可以及时发现性能问题、内存泄漏等问题,并进行有效诊断。
为什么需要JVM监控?
监控的价值
监控指标
| 指标类别 | 具体指标 | 说明 |
|---|---|---|
| 内存 | 堆内存使用、非堆内存使用 | 内存是否充足 |
| GC | GC次数、GC时间、GC原因 | GC是否健康 |
| 线程 | 线程数、线程状态、死锁 | 线程是否正常 |
| 类加载 | 加载类数、卸载类数 | 类加载是否正常 |
| CPU | CPU使用率 | 计算资源是否充足 |
JDK自带工具
jps - JVM进程状态工具
# 列出所有Java进程
jps
# 显示完整包名
jps -l
# 显示传递给main方法的参数
jps -m
# 显示JVM参数
jps -v
# 组合使用
jps -lmv
输出示例:
12345 com.example.Application --spring.profiles.active=prod
12346 sun.tools.jps.Jps -lmv
jstat - JVM统计信息监控
# 查看GC统计信息
jstat -gc <pid> 1000
# 查看GC汇总
jstat -gcutil <pid> 1000
# 查看类加载统计
jstat -class <pid>
# 查看编译统计
jstat -compiler <pid>
输出解读:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
512.0 512.0 0.0 448.0 4096.0 2048.0 10240.0 5120.0 4864.0 4608.0 512.0 480.0 12 0.150 2 0.500 0.650
S0C/S1C: Survivor区容量
S0U/S1U: Survivor区使用量
EC/EU: Eden区容量/使用量
OC/OU: 老年代容量/使用量
MC/MU: 元空间容量/使用量
YGC: Young GC次数
YGCT: Young GC时间
FGC: Full GC次数
FGCT: Full GC时间
GCT: 总GC时间
jmap - 内存映射工具
# 生成堆转储文件
jmap -dump:format=b,file=heap.hprof <pid>
# 查看堆配置
jmap -heap <pid>
# 查看对象统计(直方图)
jmap -histo <pid> | head -30
# 查看类加载器统计
jmap -clstats <pid>
应用场景:
# 1. 发现内存泄漏时生成堆转储
jmap -dump:live,format=b,file=heap.hprof <pid>
# 2. 分析内存占用
jmap -histo:live <pid> | head -20
jstack - 线程堆栈工具
# 打印线程堆栈
jstack <pid>
# 检测死锁
jstack -l <pid>
# 输出到文件
jstack <pid> > thread_dump.txt
输出解读:
"main" #1 prio=5 os_prio=0 cpu=1000.00ms elapsed=100.00s tid=0x00007f8b4c001800 nid=0x1234 waiting on condition [0x00007f8b4c001000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park([email protected]/Native Method)
- parking to wait for <0x000000076b5c7d58> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park([email protected]/LockSupport.java:341)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block([email protected]/AbstractQueuedSynchronizer.java:506)
线程名: main
线程ID: #1
优先级: 5
线程状态: WAITING (parking)
当前方法: Unsafe.park
等待对象: AbstractQueuedSynchronizer$ConditionObject
jinfo - 配置信息工具
# 查看JVM参数
jinfo -flags <pid>
# 查看系统属性
jinfo -sysprops <pid>
# 查看特定参数
jinfo -flag MaxHeapSize <pid>
# 动态修改参数(仅部分参数支持)
jinfo -flag +PrintGCDetails <pid>
jcmd - 多功能诊断工具
# 列出所有Java进程
jcmd -l
# 查看进程帮助
jcmd <pid> help
# 查看VM信息
jcmd <pid> VM.version
jcmd <pid> VM.flags
jcmd <pid> VM.command_line
# 查看线程信息
jcmd <pid> Thread.print
# 生成堆转储
jcmd <pid> GC.heap_dump filename=heap.hprof
# 查看GC信息
jcmd <pid> GC.run
jcmd <pid> GC.class_histogram
可视化工具
VisualVM
JDK自带的可视化监控工具:
# 启动VisualVM
jvisualvm
# 或
visualvm
功能:
- 实时监控CPU、内存、GC
- 线程监控和死锁检测
- 堆转储分析
- 性能分析(Profiler)
JConsole
JMX监控工具:
# 启动JConsole
jconsole
# 连接远程进程
jconsole service:jmx:rmi:///jndi/rmi://host:port/jmxrmi
监控页面:
- 概览:CPU、内存、线程、类
- 内存:堆、非堆、内存池
- 线程:线程数、死锁检测
- 类:加载类数
- VM摘要:JVM信息
Java Mission Control (JMC)
Oracle提供的高级监控工具:
# 启动JMC
jmc
功能:
- 低开销的性能分析
- 飞行记录器(Flight Recorder)
- 实时内存分析
- 线程分析
第三方工具
Arthas
阿里开源的Java诊断工具:
# 下载并启动
wget https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 常用命令
dashboard # 系统仪表盘
thread # 线程信息
jvm # JVM信息
memory # 内存信息
gc # GC信息
# 方法诊断
watch com.example.Service getOrder '{params,returnObj,throwExp}' -x 2
trace com.example.Service getOrder
stack com.example.Service getOrder
# 反编译
jad com.example.Service
# 查看方法入参和返回值
tt -t com.example.Service getOrder
MAT (Memory Analyzer Tool)
Eclipse提供的堆转储分析工具:
# 分析堆转储文件
./ParseHeapDump.sh heap.hprof
功能:
- 查找内存泄漏嫌疑
- 分析对象引用链
- 计算对象保留大小
- 生成泄漏报告
async-profiler
低开销的Java性能分析工具:
# CPU分析
./profiler.sh -d 30 -f cpu.html <pid>
# 内存分配分析
./profiler.sh -d 30 -e alloc -f alloc.html <pid>
# 锁竞争分析
./profiler.sh -d 30 -e lock -f lock.html <pid>
监控平台集成
Prometheus + Grafana
// 添加依赖
// micrometer-registry-prometheus
// 配置Prometheus端点
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "myapp");
}
监控指标:
- jvm_memory_used_bytes
- jvm_gc_pause_seconds
- jvm_threads_live_threads
- jvm_classes_loaded_classes
Elastic APM
# 启动Java应用时添加agent
java -javaagent:/path/to/elastic-apm-agent.jar \
-Delastic.apm.service_name=my-service \
-Delastic.apm.server_url=http://apm-server:8200 \
-jar myapp.jar
常见问题的诊断
内存泄漏诊断
步骤:
-
确认问题
jstat -gcutil <pid> 1000
# 观察老年代使用率是否持续增长 -
生成堆转储
jmap -dump:live,format=b,file=heap.hprof <pid> -
分析堆转储
- 使用MAT分析 dominator_tree
- 查找保留大小最大的对象
- 分析引用链
-
定位代码
// 常见内存泄漏场景
public class MemoryLeakExample {
private static final List<Object> cache = new ArrayList<>();
public void addToCache(Object obj) {
cache.add(obj); // 对象永远不会被释放
}
}
线程死锁诊断
步骤:
-
检测死锁
jstack -l <pid> | grep -A 20 "Found one Java-level deadlock" -
分析死锁
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f8b4c001000 (object 0x000000076b5c7d58)
which is held by "Thread-2"
"Thread-2":
waiting to lock monitor 0x00007f8b4c002000 (object 0x000000076b5c7d60)
which is held by "Thread-1" -
解决死锁
- 统一加锁顺序
- 使用tryLock
- 避免嵌套锁
CPU过高诊断
步骤:
-
找到CPU高的进程
top -
找到CPU高的线程
top -H -p <pid> -
转换线程ID
printf "%x\n" <thread_id> -
查看线程堆栈
jstack <pid> | grep -A 20 <hex_thread_id> -
使用async-profiler
./profiler.sh -d 30 -f cpu.html <pid>
GC问题诊断
步骤:
-
分析GC日志
# 使用GC日志分析工具
java -jar gceasy.jar gc.log -
常见GC问题
- 频繁Full GC:老年代空间不足
- GC时间长:堆内存过大或收集器选择不当
- 内存泄漏:对象无法被回收
-
优化策略
# 增大堆内存
-Xmx4g -Xms4g
# 使用G1收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
监控最佳实践
1. 建立监控基线
# 收集正常运行时的指标
jstat -gcutil <pid> 60000 > baseline_gc.log
2. 设置告警阈值
| 指标 | 警告阈值 | 严重阈值 |
|---|---|---|
| 堆内存使用率 | 70% | 85% |
| GC时间占比 | 5% | 10% |
| Full GC频率 | 1次/小时 | 1次/10分钟 |
| 线程数 | 200 | 500 |
3. 定期生成堆转储
# 定时任务生成堆转储
0 2 * * * jmap -dump:live,format=b,file=/backup/heap_$(date +\%Y\%m\%d).hprof <pid>
4. 保留GC日志
# 配置GC日志滚动
-Xlog:gc*:file=/var/log/gc.log::filecount=5,filesize=100m
小结
JVM监控与诊断是保障应用稳定运行的重要手段:
- 命令行工具:jps、jstat、jmap、jstack、jinfo、jcmd
- 可视化工具:VisualVM、JConsole、JMC
- 第三方工具:Arthas、MAT、async-profiler
- 监控平台:Prometheus、Elastic APM
- 问题诊断:内存泄漏、线程死锁、CPU过高、GC问题
建立完善的监控体系,可以及时发现和解决问题,保障应用稳定运行。