一次结合 ChatGPT 进行 JVM 线上实战优化

282 阅读3分钟

问题是什么?

  • 线上某服务 YoungGC 频繁 141.2K 次/天
  • 线上某服务 YoungGC 耗时 1.3h/天 在这里插入图片描述

解决方案

dump 线上服务 jvm heap profile(即 hprof 文件)以及 gc 日志

通过调研,大概有以下 3 种方式

方法一

该服务增加 1 个 Pod 并增加 jvm 参数(推荐使用这种方式)

注意:线上机器开启 dump 要慎重,通过 GC 日志发现这个过程持续二十多秒,FullGC 前后大约持续五十多秒,而且 FullGC 次数多了容易把磁盘打挂,因为 FullGC 前后会生成 hprof 文件而这个文件又比较大,所以要跟进观察收集文件后及时停止 dump

// 指定GC日志文件的路径,该文件记录了垃圾收集器的活动信息
-Xloggc:/tmp/gc.log 

// 在进行Full GC(全局垃圾收集)之前打印类直方图。类直方图提供了有关Java堆中类及其实例数量的信息
-XX:+PrintClassHistogramBeforeFullGC 

// 在进行Full GC 之后打印类直方图
-XX:+PrintClassHistogramAfterFullGC 

// 在进行Full GC 之前生成Java堆的堆转储(Heap Dump)。堆转储是Java堆内存中对象的快照,可用于分析内存泄漏等问题
-XX:+HeapDumpBeforeFullGC 

// 在进行Full GC 之后生成 hprof 文件
-XX:+HeapDumpAfterFullGC 

// 指定 hprof 文件的输出路径
-XX:HeapDumpPath=/tmp

// 打印详细的GC信息,包括每个垃圾收集事件的详细情况
-XX:+PrintGCDetails 

// 在每次GC之后打印堆的详细信息,包括堆的使用情况和各代的情况
-XX:+PrintHeapAtGC 

// 打印有关对象年龄分布的信息。在垃圾收集中,对象的年龄对于确定它们何时被移动到老年代是重要的
-XX:+PrintTenuringDistribution

方法二

使用 VisualVM dump 本地 Java 进程(不适合线上调试,本地跑服务可以) 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

方法三

Arathas heapdump 命令 dump 具体可查看:arthas heapdump

使用 ChatGPT 分析 GC 日志

把 GC 日志丢给 ChatGPT ,马上定位到最关键的问题「存在大对象」,根据 GC 日志提示只需验证是否真的存在大对象,以及大对象是什么问题就能迎刃而解了 在这里插入图片描述

hprof 文件分析

方法一

使用 VisualVM 分析,VisualVM 官网下载地址

分析结果 1.char 数组占用了 96.1% 的内存 在这里插入图片描述 2.大对象主要的类是 MissionRewardKey 占了 6.6% 的内存 在这里插入图片描述

方法二

使用 JProfiler 分析,JProfiler 官网下载地址

分析结果 1.大对象主要的类是 MissionRewardKey 占了 51% 的内存 在这里插入图片描述 2.通过 Graph 观察引用关系拓扑图 在这里插入图片描述

结论:存在 MissionRewardKey 大对象,影响 GC 性能

查找代码

在这里插入图片描述 通过代码可以发现,每次查询都会导致 popupIconUrl 和 barIconUrl 前边就会多拼接一个 “域名”,而 MissionRewardKey 又是一个枚举,所以导致 popupIconUrl 和 barIconUrl 占用内存不断膨胀无法被 GC 回收

注意:实际生产过程中枚举不能对外暴露 set 方法,否则就会出现这种枚举属性被篡改,正确做法只需每次查询获取 popupIconUrl 和 barIconUrl 再拼接域名即可,无需 set 枚举字段

优化后效果

在这里插入图片描述