JVM 异常问题排查是Java开发中必备的技能,常见问题包括 内存溢出(OOM) 、高CPU使用率、死锁、频繁GC、类加载异常 等。以下是详细的排查流程和工具使用指南:
一、通用排查流程
-
确认问题现象
- 明确异常类型(如
OutOfMemoryError、StackOverflowError)、日志中的关键错误信息、是否可复现。 - 检查系统监控数据(CPU、内存、磁盘、网络)。
- 明确异常类型(如
-
收集现场信息
- 日志文件:应用日志、GC日志(通过
-Xloggc:/path/to/gc.log开启)。 - 堆转储(Heap Dump) :通过
jmap -dump:format=b,file=heap.hprof <pid>或 JVM 参数-XX:+HeapDumpOnOutOfMemoryError自动生成。 - 线程转储(Thread Dump) :通过
jstack <pid> > thread.txt或kill -3 <pid>生成。
- 日志文件:应用日志、GC日志(通过
-
分析工具选择
- 命令行工具:
jstat、jmap、jstack、jinfo。 - 图形化工具:VisualVM、JConsole、MAT(Memory Analyzer Tool)、Arthas。
- 在线诊断:Arthas 动态追踪方法调用、监控线程状态。
- 命令行工具:
-
定位根因
- 结合堆/线程转储、日志、代码逻辑分析问题。
- 复现问题(如压测环境模拟)。
-
验证与修复
- 调整JVM参数(如堆大小、GC算法)、优化代码逻辑、修复资源泄漏。
二、常见问题排查详解
1. 内存溢出(OutOfMemoryError)
-
现象:
java.lang.OutOfMemoryError: Java heap space(堆内存溢出)或Metaspace(元空间溢出)。 -
排查步骤:
-
使用
jmap -heap <pid>查看堆内存分配情况。 -
分析堆转储(如MAT工具):
- 查找 支配树(Dominator Tree) 中的大对象。
- 检查 重复对象(如未关闭的数据库连接、集合缓存泄漏)。
-
检查代码中的静态集合、缓存策略(如未设置TTL)、文件/网络流未关闭。
-
-
示例命令:
# 生成堆转储 jmap -dump:live,format=b,file=heap.hprof <pid> # 查看堆内存统计 jmap -histo:live <pid> | head -n 20
2. CPU使用率过高
-
现象:应用进程占用CPU持续超过80%。
-
排查步骤:
-
使用
top -H -p <pid>找到高CPU的线程ID。 -
将线程ID转换为16进制(如
printf "%x\n" 12345)。 -
通过
jstack <pid>查看线程栈,定位代码位置。 -
常见原因:
- 死循环(如
while(true)未合理退出)。 - 频繁GC(Full GC 导致CPU飙升)。
- 复杂算法计算(如正则表达式回溯、大数组排序)。
- 死循环(如
-
-
示例命令:
# 查找高CPU线程 top -H -p <pid> # 分析线程栈 jstack <pid> | grep -A 20 <nid_16进制>
3. 线程死锁
-
现象:应用无响应,日志中可见
Found one Java-level deadlock。 -
排查步骤:
- 使用
jstack <pid>或 VisualVM 的线程分析功能。 - 查看线程状态是否为
BLOCKED,并检查锁持有链。 - 修复代码中的锁顺序问题(如按固定顺序获取锁)。
- 使用
-
示例输出:
"Thread-1" #12 prio=5 os_prio=0 tid=0x00007f... waiting to lock <0x000000076bf62200> (a A) held by "Thread-0"
4. 频繁GC导致性能下降
-
现象:应用延迟增加,GC日志中
Full GC次数增多。 -
排查步骤:
-
使用
jstat -gcutil <pid> 1000查看各分区内存使用率和GC时间。 -
分析GC日志(通过
-XX:+PrintGCDetails -XX:+PrintGCDateStamps开启)。 -
调整JVM参数:
- 增大堆大小(
-Xmx、-Xms)。 - 更换GC算法(如G1替代CMS)。
- 调整新生代/老年代比例(
-XX:NewRatio)。
- 增大堆大小(
-
-
示例参数:
# G1 GC参数示例 -XX:+UseG1GC -Xmx4g -Xms4g -XX:MaxGCPauseMillis=200
5. 类加载异常
-
现象:
ClassNotFoundException、NoClassDefFoundError。 -
排查步骤:
- 检查类路径(
-classpath)是否正确。 - 使用
-verbose:class输出类加载信息。 - 分析依赖冲突(如Maven的
mvn dependency:tree)。
- 检查类路径(
三、高级工具推荐
-
Arthas
- 动态跟踪方法调用:
trace com.example.Service *Method。 - 监控方法执行耗时:
monitor -c 5 com.example.Service getData。 - 查看类加载信息:
sc -d com.example.MyClass。
- 动态跟踪方法调用:
-
MAT(Memory Analyzer Tool)
- 分析堆转储,快速定位内存泄漏对象。
- 支持 Leak Suspects Report 自动生成泄漏报告。
-
Async-Profiler
- 生成火焰图,分析CPU、内存、锁性能瓶颈。
四、注意事项
- 生产环境谨慎操作:生成堆转储可能导致应用暂停,建议在低峰期操作。
- 监控与告警:提前配置 Prometheus + Grafana 监控JVM状态。
- 代码最佳实践:避免静态集合持有大数据、及时关闭资源(如
try-with-resources)。
通过以上流程和工具,可系统化解决大多数JVM问题。若需深入特定场景(如Native内存泄漏),需结合操作系统工具(如 pmap、gdb)进一步分析。