vlambda博客
学习文章列表

JVM垃圾回收优化实战-G1垃圾回收器







问题产生

下午对系统进行例行检查,发现项目的CPU的曲线不正常,经常会出现一个小高峰,但是在当时的流量并没有明显的变化,由此展开了今日的排查

垃圾回收CPU使用率

JVM堆

查看堆的使用情况,在最近的15分钟之内,JVM内存的变化不规则,没有呈现出锯齿状。

JVM垃圾回收优化实战-G1垃圾回收器

垃圾回收-堆内存

接着查看GC的情况

JVM垃圾回收优化实战-G1垃圾回收器

垃圾回收次数

其实问题已经非常的明显了,我们解析一下上面的这张图。

左边的1分钟内垃圾回收次数 , 可以看到一分钟内YGC的次数最高达到了70次左右, 同时还有伴有1.3次Full GC (Ergonomics),

Ergonomics翻译成中文,一般都是“人体工程学”。在JVM中的垃圾收集器中的Ergonomics就是负责自动的调gc暂停时间和吞吐量之间的平衡,然后你的虚拟机性能更好的一种做法。

再看暂停时长,已经达到了恐怖的 1.7s , 这个已经严重影响了系统的正常运行,然后从右边的那个小图上看, 内存分配的情况, 年轻代的要求分配内存达到了500M, 从第一张图JVM的内存结构上看,整个年轻代的内存在623M , 在这种情况下,每次YGC之后都分配不了这么多内存,所以只能一部分大对象往老年代塞,其他的继续YGC,然后看内存是否可以够用,如此往复,整个系统的CPU和负载一瞬间全部上去了。

JVM垃圾回收优化实战-G1垃圾回收器

老年代内存情况

通过查看接口监控,发现这这个时间段有一个导出Excel 的功能在执行

JVM垃圾回收优化实战-G1垃圾回收器

监控接口导出Excel

通过查看代码分析,该接口会在循环里面构建大量的对象,这些对象都是朝生夕死的。除非一些大的对象,很少一部分才会进入老年代。

基于这种场景,作出如下调整, 由于该项目目前是一个非常大的单体应用,2个G的内存够呛,年轻代600多M在出现一些稍微耗费内存的情况就支撑不了。

因此堆内存提高到4G, 4G的堆内存,垃圾回收器采取G1回收器,G1回收器网上有很多介绍,

JVM垃圾回收优化实战-G1垃圾回收器

在G1中,还有一种特殊的区域,叫Humongous区域。如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象。这些巨型对象,默认直接会被分配在年老代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

他废除了年轻代和老年代的空间区分,取而代之的是一个一个的Region , 默认一个Region的大小是2M, 如果我设置了4G的内存之后,那么默认就是 (1024*4)/2 = 2048个regions , 鉴于本服务存在很多文件读取解析,会有大对象的产生,减少进入H区的对象, 所以Region的大小设置为4M。

G1回收器有个非常好的特性就是会不断的帮助JVM调整策略, 会根据实际的GC情况调整年轻代和老年代的比例大小,默认情况下,年轻代最多可以占用60%的堆内存。这其实就是GC的灵活性。

G1的另一个显著特点他能够让用户设置应用的暂停时间,通过参数:-XX:MaxGCPauseMillis来指定,为什么G1能做到这一点呢?也许你已经注意到了,G1回收的第4步,它是“选择一些内存块”,而不是整代内存来回收,这是G1跟其它GC非常不同的一点,其它GC每次回收都会回收整个Generation的内存(Eden, Old), 而回收内存所需的时间就取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如。

(这段话网上摘录,据说是阿里的面试题

通过上面的分析,最把启动命令改成下面这样

nohup java -jar -Denv=pro -Dserver.port=8080   -Xms4g -Xmx4g -XX:+UseG1GC -XX:G1HeapRegionSize=4m -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError  ./x x x.jar & 

使用命令查看堆内存设置,发现一切生效。

[root@localhost ~]# jmap -heap 10181
Attaching to process ID 10181, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.231-b11

using thread-local object allocation.
Garbage-First (G1) GC with 2 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4294967296 (4096.0MB) # 最大堆内存
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 2575302656 (2456.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB) 
   NewRatio                 = 2 # 新生代和老年代的比例, 就是说老年代和新生代的比例是2:1
   SurvivorRatio            = 8 # 新生代比例
   MetaspaceSize            = 21807104 (20.796875MB) # 元空间
   CompressedClassSpaceSize = 1073741824 (1024.0MB) # 对象压缩指针空间
   MaxMetaspaceSize         = 17592186044415 MB # 最大元空间,受限于物理机的内存
   G1HeapRegionSize         = 4194304 (4.0MB) # 每个Region的大小

Heap Usage:
G1 Heap:
   regions  = 1024 # 总的Region数量
   capacity = 4294967296 (4096.0MB) # 总内存
   used     = 1047525872 (998.9985198974609MB) # 已使用
   free     = 3247441424 (3097.001480102539MB) #剩余
   24.389612302184105% used
G1 Young Generation:
Eden Space:
   regions  = 221 # Eden区使用的region的数量
   capacity = 2185232384 (2084.0MB) # Eden总内存
   used     = 926941184 (884.0MB) # Eden已使用
   free     = 1258291200 (1200.0MB) #Eden剩余
   42.41842610364683% used
Survivor Space:
   regions  = 8 # Survivor所占的Region的数量
   capacity = 33554432 (32.0MB)
   used     = 33554432 (32.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 21 # 老年代使用的Region
   capacity = 2076180480 (1980.0MB) # 老年代大小
   used     = 87030256 (82.99851989746094MB)
   free     = 1989150224 (1897.001480102539MB)
   4.1918444392657035% used

项目上线后,在同样执行文件导出的接口(该接口执行了一分钟),整体的GC情况正常,CPU正常。

后续继续观察系统的运行情况,有问题再做调整吧!

JVM垃圾回收优化实战-G1垃圾回收器





回复:“资源”、“架构”等关键词获取海量免费学习资料。