JVM 常见异常问题排查

143 阅读4分钟

JVM 异常问题排查是Java开发中必备的技能,常见问题包括 内存溢出(OOM)高CPU使用率死锁频繁GC类加载异常 等。以下是详细的排查流程和工具使用指南:


一、通用排查流程

  1. 确认问题现象

    • 明确异常类型(如 OutOfMemoryErrorStackOverflowError)、日志中的关键错误信息、是否可复现。
    • 检查系统监控数据(CPU、内存、磁盘、网络)。
  2. 收集现场信息

    • 日志文件:应用日志、GC日志(通过 -Xloggc:/path/to/gc.log 开启)。
    • 堆转储(Heap Dump) :通过 jmap -dump:format=b,file=heap.hprof <pid> 或 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError 自动生成。
    • 线程转储(Thread Dump) :通过 jstack <pid> > thread.txtkill -3 <pid> 生成。
  3. 分析工具选择

    • 命令行工具jstatjmapjstackjinfo
    • 图形化工具:VisualVM、JConsole、MAT(Memory Analyzer Tool)、Arthas。
    • 在线诊断:Arthas 动态追踪方法调用、监控线程状态。
  4. 定位根因

    • 结合堆/线程转储、日志、代码逻辑分析问题。
    • 复现问题(如压测环境模拟)。
  5. 验证与修复

    • 调整JVM参数(如堆大小、GC算法)、优化代码逻辑、修复资源泄漏。

二、常见问题排查详解

1. 内存溢出(OutOfMemoryError)
  • 现象java.lang.OutOfMemoryError: Java heap space(堆内存溢出)或 Metaspace(元空间溢出)。

  • 排查步骤

    1. 使用 jmap -heap <pid> 查看堆内存分配情况。

    2. 分析堆转储(如MAT工具):

      • 查找 支配树(Dominator Tree) 中的大对象。
      • 检查 重复对象(如未关闭的数据库连接、集合缓存泄漏)。
    3. 检查代码中的静态集合、缓存策略(如未设置TTL)、文件/网络流未关闭。

  • 示例命令

    # 生成堆转储
    jmap -dump:live,format=b,file=heap.hprof <pid>
    ​
    # 查看堆内存统计
    jmap -histo:live <pid> | head -n 20
    
2. CPU使用率过高
  • 现象:应用进程占用CPU持续超过80%。

  • 排查步骤

    1. 使用 top -H -p <pid> 找到高CPU的线程ID。

    2. 将线程ID转换为16进制(如 printf "%x\n" 12345)。

    3. 通过 jstack <pid> 查看线程栈,定位代码位置。

    4. 常见原因:

      • 死循环(如 while(true) 未合理退出)。
      • 频繁GC(Full GC 导致CPU飙升)。
      • 复杂算法计算(如正则表达式回溯、大数组排序)。
  • 示例命令

    # 查找高CPU线程
    top -H -p <pid>
    ​
    # 分析线程栈
    jstack <pid> | grep -A 20 <nid_16进制>
    
3. 线程死锁
  • 现象:应用无响应,日志中可见 Found one Java-level deadlock

  • 排查步骤

    1. 使用 jstack <pid> 或 VisualVM 的线程分析功能。
    2. 查看线程状态是否为 BLOCKED,并检查锁持有链。
    3. 修复代码中的锁顺序问题(如按固定顺序获取锁)。
  • 示例输出

    "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 次数增多。

  • 排查步骤

    1. 使用 jstat -gcutil <pid> 1000 查看各分区内存使用率和GC时间。

    2. 分析GC日志(通过 -XX:+PrintGCDetails -XX:+PrintGCDateStamps 开启)。

    3. 调整JVM参数:

      • 增大堆大小(-Xmx-Xms)。
      • 更换GC算法(如G1替代CMS)。
      • 调整新生代/老年代比例(-XX:NewRatio)。
  • 示例参数

    # G1 GC参数示例
    -XX:+UseG1GC -Xmx4g -Xms4g -XX:MaxGCPauseMillis=200
    
5. 类加载异常
  • 现象ClassNotFoundExceptionNoClassDefFoundError

  • 排查步骤

    1. 检查类路径(-classpath)是否正确。
    2. 使用 -verbose:class 输出类加载信息。
    3. 分析依赖冲突(如Maven的 mvn dependency:tree)。

三、高级工具推荐

  1. Arthas

    • 动态跟踪方法调用:trace com.example.Service *Method
    • 监控方法执行耗时:monitor -c 5 com.example.Service getData
    • 查看类加载信息:sc -d com.example.MyClass
  2. MAT(Memory Analyzer Tool)

    • 分析堆转储,快速定位内存泄漏对象。
    • 支持 Leak Suspects Report 自动生成泄漏报告。
  3. Async-Profiler

    • 生成火焰图,分析CPU、内存、锁性能瓶颈。

四、注意事项

  • 生产环境谨慎操作:生成堆转储可能导致应用暂停,建议在低峰期操作。
  • 监控与告警:提前配置 Prometheus + Grafana 监控JVM状态。
  • 代码最佳实践:避免静态集合持有大数据、及时关闭资源(如 try-with-resources)。

通过以上流程和工具,可系统化解决大多数JVM问题。若需深入特定场景(如Native内存泄漏),需结合操作系统工具(如 pmapgdb)进一步分析。