一、什么是G1
G1(Garbadge First Collector)作为一款JVM最新的垃圾收集器,可以解决CMS中Concurrent Mode Failed问题,尽量缩短处理超大堆的停顿,在G1进行垃圾回收的时候完成内存压缩,降低内存碎片的生成。
G1在堆内存比较大的时候表现出比较高吞吐量和短暂的停顿时间,而且已成为Java 9的默认收集器。未来替代CMS只是时间的问题。
二、CMS与G1对比
CMS (Concurrent Mark Sweep)收集器
运作流程
•初始标记、重新标记要停顿其他用户线程
•初始标记仅仅只是标记出GC ROOTS能直接关联到的对象,速度很快
•并发标记阶段是进行GC ROOTS 根搜索算法阶段,会判定对象是否存活
•重新标记阶段则修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录->停顿时间会比初始标记阶段稍长,但比并发标记阶段要短
G1(Garbadge First Collector)收集器
运作流程
初始标记:标记出所有与根节点直接关联引用对象、需要STW
并发标记:遍历之前标记到的关联节点,继续向下标记所有存活节点。在此期间所有变化引用关系的对象,都会被记录在Remember Set Logs中
最终标记:标记在并发标记期间,新产生的垃圾。需要STW
筛选回收:根据用户指定的期望回收时间回收价值较大的对象,需要STW
CMS收集器的三个显著缺点:
•CMS收集器对CPU资源非常敏感
在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4。
•CMS收集器无法处理浮动垃圾
可能出现“Concurrent Mode Failure“,失败后而导致另一次Full GC的产生
由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自热会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。这一部分垃圾称为“浮动垃圾”
需要预留一部分内存空间提供并发收集时的程序运作使用
•基于“标记-清除”算法实现的收集器,产生大量碎片
空间碎片太多时,将会给对象分配带来很多麻烦,比如说大对象,内存空间找不到连续的空间来分配不得不提前触发一次Full GC->提供了一个-XX:UseCMSCompactAtFullCollection开关参数->用于在Full GC之后增加一个碎片整理过程->通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。
G1相对CMS的优点:
•停顿时间可以预测:
G1可以指定时间,在指定时间内只回收部分价值最大的空间,而CMS需要扫描整个年老代,无法预测停顿时间
•无内存碎片:
垃圾回收后会整合空间,CMS采用"标记-清理"算法,存在内存碎片
•筛选回收阶段不会产生"浮动垃圾":
由于只回收部分region,所以STW时间我们可控,所以不需要与用户线程并发争抢CPU资源,而CMS并发清理需要占据一部分的CPU,会降低吞吐量。
由于STW,所以不会产生"浮动垃圾"(即CMS在并发清理阶段产生的无法回收的垃圾)
三、我的应用是否适合升级G1?
以下场景下G1更适合
1.服务端多核CPU、JVM内存占用较大的应用(至少大于4G)
2.应用在运行过程中会产生大量内存碎片、需要经常压缩空间
3.想要更可控、可预期的GC停顿周期;防止高并发下应用雪崩现象
四、CMS升级G1案例
升级背景
在压测过程中发现aicloud-proxy-player存在GC时长长,而且次数多的问题。根据GC日志和观察分析发现:均是由于年轻代用满触发YGC,而且多次YGC后没有新对象晋升至老年代。此时年轻代和老年代的内存配比是1:1。dump下来了内存的堆栈信息。发现大量存在着小对象,即byte和String等对象的新建和释放,这些对象在每次进行线程请求后会生成,生成后均是一次性使用。所以当并发越高,请求越大会越大量的生成一次性的小对象,YGC也会越来越频繁。
升级G1原因
1.进行碎片处理,内存空间整理,减少消耗,对于大量小对象十分有利;
2.采用Region模式,不再GC的时候进行内存对象的拷贝,减少耗时;
3.可设置停顿时间,更大的好处在于年轻代和老年代的动态大小分配,对于我们没用的老年代很有效;
4.UseStringDeduplication 使用G1特性的去除重复的字符串,减少内存开销
升级G1效果
显著降低了GC次数,减少了GC的耗时