G1垃圾回收详解
G1垃圾回收器
G1垃圾收集器
CMS垃圾收集器的弊端:会产生内存碎片&&空间需要预留;
这俩个问题在处理的时候,很有可能会导致停顿时间过长,说白了就是CMS的停顿时间是不可预知的
而G1又可以理解为在CMS垃圾收集器上进行”升级”;
G1 垃圾收集器可以给你设定一个你希望Stop The Word 停顿时间,G1垃圾收集器会根据这个时间尽量满足你
在JVM堆的内存分布是以物理空间进行隔离
在G1垃圾收集器的世界上,堆的划分不再是物理形式,而是以逻辑的形式进行划分
不过,像之前说过的分代概念在G1垃圾收集器的世界还是一样奏效的
比如说:新对象一般会分配到Eden区、经过默认15次的Minor GC新生代的对象如果还存活,会移交到老年代等等…
来看一下G1垃圾收集器世界的堆空间分布
从图上就可以发现,堆被划分了多个同等份的区域,在G1里每个区域叫做Region
老年代、新生代、Survivor这些应该就不用我多说了吧?规则是跟CMS一样的
G1中,还有一种叫 Humongous(大对象)区域,其实就是用来存储特别大的对象(大于Region内存的一半)
一旦发现没有引用指向大对象,就可直接在年轻代的Minor GC中被回收掉
其实稍微想一下,也能理解为什么要将堆空间进行细分多个小的区域
像以前的垃圾收集器都是对堆进行物理划分
如果堆空间(内存)大的时候,每次进行垃圾回收都需要对一整块大的区域进行回收,那收集的时间是不好控制的
而划分多个小区域之后,那对这些小区域回收就容易控制它的收集时间了
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的开销
Minor GC的回收过程
Minor GC我认为可以简单分为三个步骤:根扫描、更新&&处理 RSet、复制对象
第一步应该很好理解,因为这跟之前CMS是类似的,可以理解为初始标记的过程
第二步涉及到Rset的概念
我们聊CMS回收过程的时候,同样讲到了Minor GC,它是通过卡表(cart table)来避免全表扫描老年代的对象,
因为Minor GC 是回收年轻代的对象,但如果老年代有对象引用着年轻代,那这些被老年代引用的对象也不能回收掉;
同样的,在G1也有这种问题(毕竟是Minor GC)。CMS是卡表,而G1解决跨代引用的问题的存储一般叫做RSet
只要记住,RSet这种存储在每个Region都会有,它记录着其他Region引用了当前Region的对象关系
对于年轻代的Region,它的RSet 只保存了来自老年代的引用(因为年轻代的没必要存储啊,自己都要做Minor GC了)
而对于老年代的 Region 来说,它的 RSet 也只会保存老年代对它的引用(在G1垃圾收集器,老年代回收之前,都会先对年轻代进行回收,所以没必要保存年轻代的引用)
了解了RSet,那么第二步也可以比较容易的理解,无非就是处理RSet的信息并且扫描,将老年代对象持有年轻代对象的相关引用都加入到GC Roots下,避免被回收掉
到了第三步也挺好理解的:把扫描之后存活的对象往空的Survivor区或者老年代存放,其他的Eden区进行清除
这里要提下的是,在G1还有另一个名词,叫做CSet,它的全称是 Collection Set,保存了一次GC中将执行垃圾回收的Region。CSet中的所有存活对象都会被转移到别的可用Region上。
在Minor GC 的最后,会处理下软引用、弱引用、JNI Weak等引用,结束收集
Mixed GC的回收过程
当堆空间的占用率达到一定阈值后会触发Mixed GC(默认45%,由参数决定)
Mixed GC 依赖全局并发标记统计后的Region数据
全局并发标记它的过程跟CMS非常类型,步骤大概是:初始标记(STW)、并发标记、最终标记(STW)以及清理(STW)
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上
不过SATB算法有个小的问题,就是:如果在开始时,G1就认为它是活的,那就在此次GC中不会对它回收,即便可能在并发阶段上对象已经变为了垃圾。所以,G1也有可能会存在浮动垃圾的问题
但是总的来说,对于G1而言,问题不大(毕竟它不是追求一次把所有的垃圾都清除掉,而是注重 Stop The World时间)
最后一个阶段就是清理,这个阶段也是会Stop The World的,主要清点和重置标记状态;会根据停顿预测模型(其实就是设定的停顿时间),来决定本次GC回收多少Region。
一般来说,Mixed GC会选定所有的年轻代Region,部分回收价值高的老年代Region(回收价值高其实就是垃圾多)进行采集
最后Mixed GC 进行清除还是通过拷贝的方式去干的
所以,一次回收未必是将所有的垃圾进行回收的,G1会依据停顿时间做出选择Region数量
Full GC触发时机
如果在Mixed GC中无法跟上用户线程分配内存的速度,导致老年代填满无法继续进行Mixed GC,就又会降级到serial old GC来收集整个GC heap
不过这个场景相较于CMS还是很少的,毕竟G1没有CMS内存碎片这种问题