G1收集器原理与分析
什么是G1收集器?
G1收集器全名Garbage First Garbage Collector,是一个低延迟的服务器端垃圾收集器,设计目的取代CMS收集器。
G1收集器解决了什么问题?
在G1收集器出现之前,CMS是常用的专注响应性的老年代收集器,CMS在大内存情况下gc pause时间长(remark),G1收集器的出现可以解决了应用在使用大内存的情况下gc pause时间长的问题,并且提供可预期的gc停顿时间。
G1使用什么方式解决问题?
G1收集器将内存化整为零,将堆内存分割成一个个大小相同的region,年轻代(eden区、survivor区)和老年代(old区)由多个物理上不连续的region组成,G1根据模型计算出回收收益最高,且开销时间不超过用户指定GC停顿时间的若干region,通过复制算法gc这些region。而在CMS中会回收整个老年代。
使用场景
需要使用大内存的同时限制GC停顿时间的应用,6G内存以上并且要求GC停顿时间稳定小于500ms。
使用CMS或者ParallelOld收集器的应用在满足一下任意一个条件都建议切换为G1收集器:
- 存活对象占用heap中超过50%的空间。
- 对象分配率或提升率差异很大。
- GC停顿时间超过500ms到1s。
CMS和G1有哪些区别?
- 垃圾收集范围:CMS只回收老年代,G1收集器回收年轻代和老年代。
- 垃圾收集算法:使用的垃圾收集算法不一样,cms使用标记清除算法,G1使用复制算法,产生的内存碎片更少
- 内存划分:CMS年轻代老年代参使用整块的屋里连续内存,G1的年轻代和老年代由多个物理上不连续的region组成。
- GC时间可预测:G1支持指定用户期望的最大gc停顿时间,CMS不支持。
G1收集可以分为两大部分:
- 全局并发标记(global concurrent marking)
- 拷贝存活对象(evacuation),或者叫迁移。
而这两部分可以相对独立执行。
G1 Evacuation
Evacuation阶段是全暂停的。它负责把一部分region里的活对象拷贝到空region里去,然后回收原本的region的空间。
Evacuation阶段可以自由选择任意多个region来独立收集构成收集集合(collection set,简称CSet),靠per-region remembered set(简称RSet)实现。这是regional garbage collector的特征。
G1有两种GC模式:Young GC和Mixed GC,Young GC和Mixed GC都是STW。
- Young GC:选定所有年轻代region添加到CSet中。通过控制年轻代region的个数来,即年轻代内存的大小,来控制young GC的时间开销。
- Mixed GC:选定所有年轻代region,外加根据global concurrent marking统计结果得出的收益高的若干老年代region添加到CSet中。在用户指定开销返回内尽可能选择收益高的老年代region。
在选定CSet后,evacuation其实就跟ParallelScavenge的young GC的算法类似,采用并行copying(或者叫scavenging)算法把CSet里每个region里的活对象拷贝到新的region里,整个过程完全暂停。从这个意义上说,G1的evacuation跟传统的mark-compact算法的compaction完全不同:前者会自己从根集合遍历对象图来判定对象的生死,不需要依赖global concurrent marking的结果,有就用,没有拉倒;而后者则依赖于之前的mark阶段对对象生死的判定。
CSet的选定完全靠统计模型找处收益最高、开销不超过用户指定的上限的若干region。由于每个region都有RSet覆盖,要单独evacuate任意一个或多个region都没问题。
可以看到young gen region总是在CSet内。因此分代式G1不维护从young gen region出发的引用涉及的RSet更新。
Mixed GC不是Full GC,因为Mixed GC只收集部分老年代region,如果在Mixed GC期间出现老年代被占用完的情况,JVM会采用Serial Old(Full GC)收集器来收集整个Heap。
老年代region回收完全依赖于Mixed GC。
G1的正常工作流程就是在young gc和mixed gc之间视情况切换,背后定期做global concurrent marking(全局并发标记),global concurrent marking(全局并发标记)主要是为Mixed GC提供标记服务,而不是一次GC过程中的必须环节。G1的所有concurrent动作都在global concurrent marking里。Young GC和mixed GC都是完全暂停的。
扫描任何region的时候如果碰到指向不在CSet里的region的引用都可以忽略,不只是扫描young gen region可以。
要记住的是,在一次收集中,从非收集区域到收集区域的incoming reference是重要的(需要作为根集合的一部分),而从收集区域到非收集区域的outgoing reference是可忽略的(非收集区域的对象反正不收集,可以当作还活着)。
全局并发标记(global concurrent marking)
作用:标记老年代region,提供统计结果供Mixed GC使用,选取收益高的老年代region回收,帮助回收老年代对象。
global concurrent marking基于SATB形式的并发标记,分为以下几个阶段:
-
The Initial Mark Phase(STW):扫描GC roots直接可达对象,并将他们的字段压入扫描栈(marking stack)等待后续扫描。初始标记阶段借用young gc的暂停,没有额外的单独暂停阶段,所以在The Initial Mark Phase发生在young gc之后。
The initial-mark phase is piggy backed (done at the same time) on a normal (STW) young garbage collection -
The Root Region Scanning Phase:扫描初始标记阶段中的survivor regions对老年代region的跨代引用对象,压入扫描栈(marking stack)。
-
The Concurrent Marking Phase:从扫描栈(marking stack)中递归查找所有引用可达对象。和用户线程并行执行。
-
The Remark Phase(STW):完成存活对象的标记,标记那些在并发标记阶段发生变化的对象,将被回收。
-
The Cleanup Phase (STW and Concurrent):old region存活对象情况统计(确定未使用region和Mixed GC收集候选region)(Stop the world);将空闲region重置到空闲列表中(concurrent);
重要控制参数:
什么时候触发global concurrent marking?
-XX:InitiatingHeapOccupancyPercent=45:设置触发全局扫描的堆占用百分比,默认值45%。For changing the marking threshold.
Sets the Java heap occupancy threshold that triggers a marking cycle. The default occupancy is 45 percent of the entire Java heap.
什么时候触发Mixed GC?
-XX:G1HeapWastePercent=10:设置触发Mixed GC的空间浪费阈值,默认值10%,空间浪费百分比大于10%才触发Mixed GC。
Sets the percentage of heap that you are willing to waste. The Java HotSpot VM does not initiate the mixed garbage collection cycle when the reclaimable percentage is less than the heap waste percentage. The default is 10 percent.
哪些参数影响old region 进入CSet?
-XX:G1MixedGCLiveThresholdPercent=65:设置old region中存活对象百分比,只有在此百分比以下的region才能进入Cset,默认值65%。
-XX:G1OldCSetRegionThresholdPercent=10:设置在一次mixed gc中old regions大小总和占堆比例上限,默认值10%,即一次mixed gc中回收的old regions大小总和不能超过堆大小的10%。
Young GC 和Mixed GC都是STW,为什么G1还可以被称为低延迟的GC实现呢?
可以看到在这么多步骤里,G1只有两件事是并发执行的:(1) 全局并发标记;(2) logging write barrier的部分处理。而“拷贝对象”(evacuation)这个很耗时的动作却不是并发而是完全暂停的。那G1为何还可以叫做低延迟的GC实现呢?
重点就在于G1虽然会mark整个堆,但并不evacuate所有有活对象的region;通过只选择收益高的少量region来evacuate,这种暂停的开销就可以(在一定范围内)可控。每次evacuate的暂停时间应该跟一般GC的young GC类似。所以G1把自己标榜为“软实时”(soft real-time)的GC。
CMS和G1收集器的选择:
G1需要暂停来拷贝对象,而CMS在暂停中只需要扫描(mark)对象,那算法上G1的暂停时间会比CMS短么?
其实CMS在较小的堆、合适的workload的条件下暂停时间可以很轻松的短于G1。在2011年的时候Ramki告诉我堆大小的分水岭大概在10GB~15GB左右:以下的-Xmx更适合CMS,以上的才适合试用G1。现在到了2014年,G1的实现经过一定调优,大概在6GB~8GB也可以跟CMS有一比,我之前见过有在-Xmx4g的环境里G1比CMS的暂停时间更短的案例。
合适的workload:CMS最严重的暂停通常发生在remark阶段,因为它要扫描整个根集合,其中包括整个young gen。如果在CMS的并发标记阶段,mutator仍然在高速分配内存使得young gen里有很多对象的话,那remark阶段就可能会有很长时间的暂停。Young gen越大,CMS remark暂停时间就有可能越长。所以这是不适合CMS的workload。相反,如果mutator的分配速率比较温和,然后给足时间让并发的precleaning做好remark的前期工作,这样CMS就只需要较短的remark暂停,这种条件下G1的暂停时间很难低于CMS。
G1收集器的缺点:
G1停顿时间的瓶颈主要是标记-复制中的转移阶段STW,主要是因为G1未能解决转移过程中准确定位对象地址的问题。
G1中的重要概念:
region:
SATB
作用:保证并发GC的正确性。
全称是Snapshot-At-The-Beginning,由字面理解,是GC开始时活着的对象的一个快照。它是通过Root Tracing得到的,作用是维持并发GC的正确性。 那么它是怎么维持并发GC的正确性的呢?根据三色标记算法,我们知道对象存在三种状态:
- 白:对象没有被标记到,标记阶段结束后,会被当做垃圾回收掉。
- 灰:对象被标记了,但是它的field还没有被标记或标记完。
- 黑:对象被标记了,且它的所有field也被标记完了。
由于并发阶段的存在,Mutator和Garbage Collector线程同时对对象进行修改,就会出现白对象漏标的情况,这种情况发生的前提是:
- Mutator赋予一个黑对象该白对象的引用。
- Mutator删除了所有从灰对象到该白对象的直接或者间接引用。
对于第一个条件,在并发标记阶段,如果该白对象是new出来的,并没有被灰对象持有,那么它会不会被漏标呢?Region中有两个top-at-mark-start(TAMS)指针,分别为prevTAMS和nextTAMS。在TAMS以上的对象是新分配的,这是一种隐式的标记。对于在GC时已经存在的白对象,如果它是活着的,它必然会被另一个对象引用,即条件二中的灰对象。如果灰对象到白对象的直接引用或者间接引用被替换了,或者删除了,白对象就会被漏标,从而导致被回收掉,这是非常严重的错误,所以SATB破坏了第二个条件。也就是说,一个对象的引用被替换时,可以通过write barrier 将旧引用记录下来。
SATB也是有副作用的,如果被替换的白对象就是要被收集的垃圾,这次的标记会让它躲过GC,这就是float garbage。因为SATB的做法精度比较低,所以造成的float garbage也会比较多。
RSet
作用:空间换时间的数据结构,加快从不参与收集的Region到参与收集的Region(CSet)引用定位的速度。
全称是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会在对应的RSet中记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。 这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。
下图表示了RSet、Card和Region的关系(出处):

上图中有三个Region,每个Region被分成了多个Card,在不同Region中的Card会相互引用,Region1中的Card中的对象引用了Region2中的Card中的对象,蓝色实线表示的就是points-out的关系,而在Region2的RSet中,记录了Region1的Card,即红色虚线表示的关系,这就是points-into。 而维系RSet中的引用关系靠post-write barrier和Concurrent refinement threads来维护。
post-write barrier记录了跨Region的引用更新,更新日志缓冲区则记录了那些包含更新引用的Cards。一旦缓冲区满了,Post-write barrier就停止服务了,会由Concurrent refinement threads处理这些缓冲区日志。 RSet究竟是怎么辅助GC的呢?在做YGC的时候,只需要选定young generation region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation。 而mixed gc的时候,old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量。
GC时GC Root的对象只可能在两个位置:CSet里的Region和非CSet里的Region,CSet是需要收集的region集合而非CSet里的region不需要收集,如果GC Root对象在CSet的Region里,依次遍历可达对象即可;如果GC Root在非CSet里,就需要依次遍历从非CSet到CSet里对象的引用即扫描所有非CSet region。非常耗时,如果有了RSet,通过扫描CSet里所有Region的RSet就能知道不参与收集的其他Region对CSet中对象的引用,避免了全局扫描这些不参与收集的Region
Pause Prediction Model:
Pause Prediction Model 即停顿预测模型。它在G1中的作用是: >G1 uses a pause prediction model to meet a user-defined pause time target and selects the number of regions to collect based on the specified pause time target.
G1 GC是一个响应时间优先的GC算法,它与CMS最大的不同是,用户可以设定整个GC过程的期望停顿时间,参数-XX:MaxGCPauseMillis指定一个G1收集过程目标停顿时间,默认值200ms,不过它不是硬性条件,只是期望值。那么G1怎么满足用户的期望呢?就需要这个停顿预测模型了。G1根据这个模型统计计算出来的历史数据来预测本次收集需要选择的Region数量,从而尽量满足用户设定的目标停顿时间。 停顿预测模型是以衰减标准偏差为理论基础实现的。
在这个预测计算公式中:davg表示衰减均值,sigma()返回一个系数,表示信赖度,dsd表示衰减标准偏差,confidence_factor表示可信度相关系数。而方法的参数TruncateSeq,顾名思义,是一个截断的序列,它只跟踪了序列中的最新的n个元素。
参考资料:
- https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
- https://www.oracle.com/technical-resources/articles/java/g1gc.html#Taming
- https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector.htm#JSGCT-GUID-ED3AB6D3-FD9B-4447-9EDF-983ED2F7A573
- https://hllvm-group.iteye.com/group/topic/44381
- https://tech.meituan.com/2016/09/23/g1.html
- https://www.oracle.com/java/technologies/javase/hotspot-garbage-collection.html


浙公网安备 33010602011771号