G1垃圾回收详解

G1垃圾回收器

G1垃圾收集器

CMS垃圾收集器的弊端:会产生内存碎片&&空间需要预留;

这俩个问题在处理的时候,很有可能会导致停顿时间过长,说白了就是CMS的停顿时间是不可预知的

而G1又可以理解为在CMS垃圾收集器上进行”升级”;

G1 垃圾收集器可以给你设定一个你希望Stop The Word 停顿时间,G1垃圾收集器会根据这个时间尽量满足你

在JVM堆的内存分布是以物理空间进行隔离

20220723105015

在G1垃圾收集器的世界上,堆的划分不再是物理形式,而是以逻辑的形式进行划分

不过,像之前说过的分代概念在G1垃圾收集器的世界还是一样奏效的

比如说:新对象一般会分配到Eden区、经过默认15次的Minor GC新生代的对象如果还存活,会移交到老年代等等…

来看一下G1垃圾收集器世界的空间分布

img

从图上就可以发现,堆被划分了多个同等份的区域,在G1里每个区域叫做Region

老年代、新生代、Survivor这些应该就不用我多说了吧?规则是跟CMS一样的

G1中,还有一种叫 Humongous(大对象)区域,其实就是用来存储特别大的对象(大于Region内存的一半)

一旦发现没有引用指向大对象,就可直接在年轻代的Minor GC中被回收掉

其实稍微想一下,也能理解为什么要将堆空间进行细分多个小的区域

像以前的垃圾收集器都是对堆进行物理划分

如果堆空间(内存)大的时候,每次进行垃圾回收都需要对一整块大的区域进行回收,那收集的时间是不好控制的

而划分多个小区域之后,那对这些小区域回收就容易控制它的收集时间

img

G1的工作流程

在G1收集器中,可以主要分为有Minor GC(Young GC)和Mixed GC,也有些特殊场景可能会发生Full GC

首先说一下Minor GC

G1的Minor GC其实触发时机跟前面提到过的垃圾收集器都是一样的

等到Eden区满了之后,会触发Minor GC。Minor GC同样也是会发生Stop The World的

要补充说明的是���在G1的世界里,新生代和老年代所占堆的空间是没那么固定的(会动态根据最大停顿时间进行调整)

这块要知道会给我们提供参数进行配置就好了,所以,动态地改变年轻代Region的个数可以控制Minor GC的开销

20220723105254

Minor GC的回收过程

Minor GC我认为可以简单分为三个步骤:根扫描、更新&&处理 RSet、复制对象

第一步应该很好理解,因为这跟之前CMS是类似的,可以理解为初始标记的过程

第二步涉及到Rset的概念

我们聊CMS回收过程的时候,同样讲到了Minor GC,它是通过卡表(cart table)来避免全表扫描老年代的对象,

因为Minor GC 是回收年轻代的对象,但如果老年代有对象引用着年轻代,那这些被老年代引用的对象也不能回收掉;

同样的,在G1也有这种问题(毕竟是Minor GC)。CMS是卡表,而G1解决跨代引用的问题的存储一般叫做RSet

只要记住,RSet这种存储在每个Region都会有,它记录着其他Region引用了当前Region的对象关系

img

对于年轻代的Region,它的RSet 只保存了来自老年代的引用(因为年轻代的没必要存储啊,自己都要做Minor GC了)

而对于老年代的 Region 来说,它的 RSet 也只会保存老年代对它的引用(在G1垃圾收集器,老年代回收之前,都会先对年轻代进行回收,所以没必要保存年轻代的引用)

了解了RSet,那么第二步也可以比较容易的理解,无非就是处理RSet的信息并且扫描,将老年代对象持有年轻代对象的相关引用都加入到GC Roots下,避免被回收掉

到了第三步也挺好理解的:把扫描之后存活的对象往空的Survivor区或者老年代存放,其他的Eden区进行清除

20220723105619

这里要提下的是,在G1还有另一个名词,叫做CSet,它的全称是 Collection Set,保存了一次GC中将执行垃圾回收的Region。CSet中的所有存活对象都会被转移到别的可用Region上。

在Minor GC 的最后,会处理下软引用、弱引用、JNI Weak等引用,结束收集

img

Mixed GC的回收过程

当堆空间的占用率达到一定阈值后会触发Mixed GC(默认45%,由参数决定)

Mixed GC 依赖全局并发标记统计后的Region数据

全局并发标记它的过程跟CMS非常类型,步骤大概是:初始标记(STW)、并发标记、最终标记(STW)以及清理(STW)

20220723105747

Mixed GC它一定会回收年轻代,并会采集部分老年代的Region进行回收的,所以它是一个“混合”GC。

首先是初始标记,这个过程是共用了Minor GC的 Stop The World(Mixed GC 一定会发生 Minor GC),复用了扫描GC Roots的操作。在这个过程中,老年代和新生代都会扫。总的来说,初始标记这个过程还是比较快的,毕竟没有追溯遍历。

接下来就到了并发标记,这个阶段不会Stop The World

GC线程与用户线程一起执行,GC线程负责收集各个 Region 的存活对象信息

从GC Roots往下追溯,查找整个堆存活的对象,比较耗时

接下来就到重新标记阶段,跟CMS又一样,标记那些在并发标记阶段发生变化的对象

引用变更

在G1中解决并发标记阶段导致引用变更的问题,使用的是SATB算法,可以简单理解为:在GC 开始的时候,它为存活的对象做了一次快照

并发阶段时,把每一次发生引用关系变化时旧的引用值给记下来

然后在重新标记阶段只扫描着块发生过变化的引用,看有没有对象还是存活的,加入到GC Roots

img

不过SATB算法有个小的问题,就是:如果在开始时,G1就认为它是活的,那就在此次GC中不会对它回收,即便可能在并发阶段上对象已经变为了垃圾。所以,G1也有可能会存在浮动垃圾的问题

但是总的来说,对于G1而言,问题不大(毕竟它不是追求一次把所有的垃圾都清除掉,而是注重 Stop The World时间)

最后一个阶段就是清理,这个阶段也是会Stop The World的,主要清点和重置标记状态;会根据停顿预测模型(其实就是设定的停顿时间),来决定本次GC回收多少Region。

一般来说,Mixed GC会选定所有的年轻代Region,部分回收价值高的老年代Region(回收价值高其实就是垃圾多)进行采集

最后Mixed GC 进行清除还是通过拷贝的方式去干的

所以,一次回收未必是将所有的垃圾进行回收的,G1会依据停顿时间做出选择Region数量

20220723110045

Full GC触发时机

如果在Mixed GC中无法跟上用户线程分配内存的速度,导致老年代填满无法继续进行Mixed GC,就又会降级到serial old GC来收集整个GC heap

不过这个场景相较于CMS还是很少的,毕竟G1没有CMS内存碎片这种问题

posted @ 2022-07-26 22:21  Faetbwac  阅读(1629)  评论(0编辑  收藏  举报