在官网中,是这样描述G1的:
The Garbage-First (G1) collector is a server-style garbage collector,
targeted for multi-processor machines with large memories. It meets
garbage collection (GC) pause time goals with a high probability,
while achieving high throughput. The G1 garbage collector is fully
supported in Oracle JDK 7 update 4 and later releases. The G1
collector is designed for applications that: Can operate concurrently
with applications threads like the CMS collector. Compact free space
without lengthy GC induced pause times. Need more predictable GC
pause durations. Do not want to sacrifice a lot of throughput
performance. Do not require a much larger Java heap.
从官网的描述中,我们知道G1是一种服务器端的垃圾收集器,应用在多处理器和大内量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。它是专门针对以下应用场景设计的:
G1收集器的设计目标是取到CMS收集器,它同CMS相比,在一下方面表现的更出色:
有了以上的特性,难怪有人说它是一款驾驭一切的垃圾收集器。
在G1的实现过程中,引入了一些新的概念,对于实现高吞吐、没有内存碎片、收集时间可控等功能起到了关键作用。
传统的GC收集器将连续的内存空间划分为新生代、老年代和永久代(JDK 8去除了永久代,引入了元空间MateSpace),这种划分的特点是各代的存储地址(逻辑地址)是连续的。如下图所示:
而G1的各代存储地址是不连续的,每一代都使用了n个不连续的大小相同的Region,每个Region占有一块连续的虚拟内存地址。如下图所示:
在上图中,我们注意到了还有一些Region标明了H,它代表Humougous,这表示这些Region存储的是巨型对象(H-obj),即大小大于等于region一半的对象。H-obj有如下几个特征:
为了减少连续H-objs分配对GC的影响,需要把大对象便为普通的对象,建议增大Region Size。
一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围从1M到32M,且是2的指数。
如果不设定,那么G1会根据Heap大小自动决定。
全称是Snopashot-At-Beginning,由字面理解,是GC开始时活着的对象的一个快照。它是通过Root Tracting得到的,作用是维持并发GC的正确性。
那么它是怎么维持并发GC的正确性呢?根据三色标记算法,我们知道对象存在三种状态:
由于并发阶段的存在,Mutator和Garbage Collector线程同时对对象进行修改,就会出现白对象漏标的情况,这种情况发生的前提是:
对于第一个条件,在并发标记阶段,如果该白对象是new出来的,并没有被灰对象持有,那么它会不会被漏标呢?Region中有两个top-at-mark-start(TAMS)指针,分别是prevTAMS和nextTAMS。在TAMS以上的对象是新分配的,这是一种隐式的标记。对于在GC时已经存在的白对象,如果它是活着的,它必然会被另一个对象引用,即条件二中的灰对象。如果灰对象到白对象的直接引用或者间接引用被替换了,或者删除了,白对象就会漏标,从而导致被回收掉,这是非常严重的错误,所以SATB破坏了第二个条件。也就是说,一个对象的引用被替换时,可以通过write barrier将旧引用记录下来。
SATB也是有副总用的,如果被替换的白对象就是要被收集的垃圾,这次的标记会让它躲过GC,这就是float garbage。因为SATB的做法精度比较低,所以造成的float garbage也会比较多。
全称是Remembered Set,是辅助GC过程的一种结构,典型的空间换时间工作,和Card Table有些类似。还有一种数据结构也是辅助GC的:Collection Set(CSet),它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。
逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于一种points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card范围内。这个RSet其实是一个HashTable,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。
下图表示了RSet、Card和Region的关系:
上图中有三个Region,每个Region被分成了多个Card,在不同的Region中的Card会互相引用,Region1中的Card对象引用了Region2中的Card对象,蓝色实线表示的就是point-out的关系,而在Region2中的RSet中,记录了Region1的Card,即红色虚线表示的关系,这就是point-into。
而维系RSet的引用关系靠post-write barrier和Concurrent refinement threads来维护。
post-write barrier记录了跨Region的引用更新,更新日志缓冲区则记录了那些包含更新引用的Cards。一旦缓冲区满了,Post-write barrier就停止服务了,会由Concurrent refinement threads处理这些缓冲区日志。
Pause Prediction Model即停顿预测模型。
G1 GC是一个响应时间优先的GC算法,它与CMS最大的不同是,用户可以设定整个GC过程的期望停顿时间,参数-XX:MaxGCPauseMillis指定一个G1收集过程目标停顿时间,默认值200ms,不过它不是硬性条件,只是期望值。那么G1怎么满足用户的期望呢?这需要这个停顿预测模型了。G1根据这个模型统计计算出来的历史数据来预测本次收集需要选择的Region数量,从而尽量满足用户设定的目标停顿时间。
G1提供了两种GC模式,Young GC和Mixed GC,两种都是完全Stop The World的。
由上面的描述克制,Mixed GC不是full GC,它只能回收部分老年代的Region,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供fullGC的。
上文中,多次提到了global concuurent marking,它的执行过程类似CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为四个步骤:
Young GC发生的时机大家都知道,那什么时候发生Mixed GC呢?其实是由一些参数控制着的,另外也控制着哪些老年代Region会被选入CSet。