概述
G1GC具备软实时性,因为它具备两个功能
1、设置期望暂停时间(最后期限):支持用户自定义mutator暂停时间
2、可预测性:预测下次GC的暂停时间,通过延迟执行GC、拆分GC目标对象来遵守1中设置的期望暂停时间。
背景
缩短最大暂停时间和保持mutator的吞吐量是两个需要平衡的指标。增量GC主要为了缩短最大暂停时间,但是会导致mutator的吞吐量下降。
G1GC让用户设置期望暂停时间,来确保吞吐量比以往的GC更好的前提下实现了软实时性。
追求软实时性的应用通常拥有巨大的堆和多核处理器。G1GC的设计能够高效的利用多处理器优势以高吞吐量处理巨大的堆。
堆结构
堆的内部被划分成大小相等的区域,所有区域排列成一排。G1GC 以区域为单位进行GC。
若正在分配对象时某个区域已满,GC线程会寻找下一个空闲的区域来继续分配。
空闲区域是通过链表进行管理的。因此查找的复杂度是O(1)
执行过程
并发标记
标记位图
表示堆中的一个区域。位图中的黑色(比特值1)表示已标记、白色(比特值0)表示未标记。
每个区域都带有两个标记位图:next和prev。
next是本次标记的标记位图,而prev是上次标记的标记位图,保存上次标记的结果。
标记位图中每个比特都对应着关联区域内对象的开头部分。
bottom
top
TAMS
标记开始时的Top(使用汤浅太一的思路GC Snapshot)
next TAMS:本次标记开始时Top的位置
prev TAMS:上次标记开始时Top的位置
执行步骤
初始标记阶段
创建位图标记next,nextTAMS指的就是开始时top所在位置。这些处理和mutator并发进行。
根扫描:对可由根直接引用的对象进行标记的过程,在根扫描阶段mutator是暂停的。
等所有区域的标记位图创建完进行根扫描。
并发标记阶段
未扫描对象:一个对象被标记,但其子对象并没有被扫描。
GC线程扫描在初始标记阶段被标记的对象,完成对大部分对象的标记。
并发标记阶段GC线程与mutator并发执行。为了避免“标记遗漏”需要使用写屏障记录GC快照(汤浅太一的方法 )。
并发标记阶段也会标记和扫描被写屏障获知变化的对象。
标记过程中新生成的对象会被看成“已完成扫描和标记”,因此其子对象不会被标记。即nextTAMS和Top之间的对象会被当成存活着的对象。
SATB
当区域内对象引用关系发生变化时,则将旧对象(非空)加入mutator所拥有的satb本地队列中。
当mutator本地的satb队列元素满了之后将队列加入全局SATB队列集合中。
GC线程会定期检查全局SATB队列集合,对其中的对象进行标记和扫描。
SATB专用写屏障优化
该算法改编自汤浅太一的算法,相比而言mutator线程不再对目标对象进行判断和标记。
由GC线程在并发标记过程中处理。这样就可以减少写屏障的开销,增加并发标记的开销。
最终标记阶段
因为未装满的SATB本地队列不会被添加到SATB队列集合中,所以在并发标记阶段结束后,各个线程的SATB本地队列可能仍然存在待扫描对象。
最终标记阶段就是扫描这些“残留的SATB本地队列”。
本步骤不能与mutator并发执行,因为mutator会修改本地SATB队列。
存活对象计数
统计区域内存活对象的字节数,然后将其存入区域内的next_marked_bytes中。
计数处理和mutator是并发执行的。
收尾工作
需要暂停mutator的运行
1、GC线程会逐个扫描每个区域,将标记位图next中的并发标记结果移动到标记位图prev中,再对并发标记中使用过的标记值进行重置,为下次并发标记做准备。
2、对没有存活对象的区域进行回收,可以把它理解为以区域为单位进行的清除处理。
3、计算每个区域的转移效率,并按照转移效率对区域进行降序排序。
next.next_marked_bytes值被移到prev.prev_marked_bytes中。prevTAMS指针移动到nextTAMS上。next.next_marked_bytes值被重置,nextTAMS指向bottom,在下次并发标记开始时再指向top位置。
转移
转移专用记忆集合
SATB队列集合主要用来记录标记过程中对象之间引用关系的变化。
转移专用记忆集合用来记录区域之间的引用关系。因为G1GC使用的是复制算法需要改变对象的地址,因此需要修改指向对象的引用关系(参考分代GC算法里的年轻代GC)。
卡表(card table)
卡表用来描述堆中一段存储空间的状态(在JDK中为512B )。
卡表的实体是数组。数组的元素是1B的卡片,对应了堆中的512B。
堆中的对象所对应的卡片在卡表中的索引值是:(对象的地址 -堆的头部地址)/512
卡片分为两种 :净卡片、脏卡片
转移专用记忆集合的结构
每个区域都有一个转移专用记忆集合,它是通过散列表实现的。
散列表的键是引用本区域的其他区域的地址,而散列表的值是一个数组,数组的元素是引用方的对象所对应的卡片索引。
区域间对象的引用关系是由转移专用记忆集合以卡片为单位粗略记录的。因此,在转移时必须扫描被记录的卡片所对应的全部对象的引用。
转移专用写屏障
当对象的域被修改时,被修改对象所对应的卡片(引用源)会被转移专用写屏障记录到转移专用记忆集合日志中。
每个mutator都有自己的转移专用记忆集合日志的缓冲区,其中存放的是卡片索引的数组。
当mutator的转移专用记忆集合日志写满后会被添加到全局的转移专用记忆集合日志的集合中。
类似SATB中的本地队列和全局队列的关系。
转移专用记忆集合维护线程
转移专用记忆集合维护线程是和mutator并发执行的
转移专用记忆集合维护线程主要进行如下工作
1、从转移专用记忆集合日志的集合(全局集合)中取出转移专用记忆集合日志(元素)
2、将卡片变为净卡片
3、检查卡片所对应存储空间内所有对象的域
4、往域中地址所指向的区域的记忆集合中添加卡片
热卡片
频繁发生修改的存储空间所对应的卡片被称为热卡片。
为了避免转移专用记忆集合线程频繁扫描热卡片,将其放入热队列中等到转移的阶段再进行处理。
执行步骤
转移阶段的3个步骤都是暂停mutator的。再转移开始后,即使并发标记正在进行也会先中断,而优先进行转移处理。
选择回收集合(Collect Set)
标准
1、转移效率高
2、转移的预测暂停时间在用户的容忍范围内
垃圾优先
回收集合的选择,会以转移效率由高到低的顺序进行。
在多数情况下,死亡对象(垃圾)越多,区域的转移效率就越高,因此G1GC会 优先选择垃圾多的区域进入回收集合。
根转移(修改指向对象的引用)
根转移的转移对象(指向对象的引用所在位置)分类
1、由根直接引用的对象
2、并发标记处理中的对象(SATB队列)
3、其他区域对象直接引用的回收集合内的对象(Remember Set)
算法
1、更新GC根、并发标记处理对象引用的回收集合对象引用地址
2、强制更新转移专用记忆集合
3、更新其他区域内对象对回收集合对象的引用地址
强制更新转移专用记忆集合(包含的数据)
1、各mutator线程的转移专用记忆集合日志
2、转移专用记忆集合日志的集合
3、热卡片
对象转移
1、将标记对象 a 转移到空闲区域
2、在转移前的区域中的forwarding指针中记录指向新对象 a' 的引用地址
3、将对象a 引用的所有位于回收集合内的对象添加到转移队列中。转移队列用来临时保存待转移对象的引用方。
4、针对对象a引用的位于回收集合外的对象,更新转移专用记忆集合(引用方的位置变更)
5、针对对对象a的引用方,更新转移专用记忆集合
转移
完成根转移之后,那些被转移队列引用的对象将会依次转移(对象转移后修改引用地址 &维护转移专用记忆集合)。直到转移队列为空,转移就完成了。
转移过程中标记信息的作用
转移专用记忆集合也在不停地记录着来自死亡对象的引用。
查看并发标记的标记信息,有助于忽略来自转移专用记忆集合中死亡对象的引用,也有助于更多地发现区域内的死亡对象。
软实时性
参数
1、可用内存上限:为了避免内存被过度占用
2、GC暂停时间上限:GC所导致的mutator的最大暂停时间
3、GC单位时间:在每个单位时间内遵守GC的暂停时间上限
定义
G1GC单位时间内GC暂停时间超过上限的次数在用户的容忍范围之内
预测转移时间
在G1GC中,由GC导致的mutator的暂停时间称为消耗。转移回收集合的消耗,等于扫描转移专用记忆集合中的卡片时的消耗与对象转移时的消耗之和。
转移回收集合的消耗由以下部分组成:
1、固定消耗:选择和释放回收集合的消耗
2、扫描脏卡片的消耗
3、针对回收集合中每个区域扫描记忆集中的卡片上的引用消耗
4、转移回收集合中每个对象及修改指向其的引用地址的消耗
GC暂停处理的调度
在一般 情况下,GC暂停处理发生的时机是可以调度的。
G1GC中有一个调度队列,其中的元素是暂停处理的开始时间和结束时间的组合(历史数据)。G1GC使用这个队列来高效地调度GC的暂停处理任务。
并发标记中的暂停处理
并发标记中需要暂停处理的3个阶段
1、初始标记阶段
2、最终标记阶段
3、收尾工作
这3个阶段需要根据历史数据预测暂停时间。
分代G1GC模式
1、区域是分代的,区域被分为新生代区域和老年代区域两类
2、回收集合的选择是分代的,minor GC会选择所有的新生代区域作为回收集合;major GC会选择所有的新生代区域及部分老年代区域作为回收集合。
无论minor GC还是major GC都会回收所有的新生代区域。
新生代区域
转移专用写屏障不会被应用在新生代。
转移专用集合中不会记录来自新生代区域的引用。
在分代GC模式中,所有新生代区域都会被选入回收集合,因此在转移新生代区域时所有对象的引用都会被检查。
分代对象转移
1、存活对象保存了自己被转移的次数即年龄
2、若转移对象年龄小于阈值则转移至survivor区域,不需要维护转移专用记忆集合(对其引用子对象)
3、若转移对象年龄大于阈值则转移至老年代,如果有对回收集合外的对象引用需要维护转移专用记忆集合
设置最大新生代区域数
完全新生代GC的最大新生代区域数是在遵守GC暂停时间上限的前提下,尽量设置较大的值。
部分新生代GC的最大新生代区域数是在遵守GC单位时间的前提下,尽量设置较小的值。
根据调度队列计算下次暂停时间,预测出“暂停时间”前能够回收多少新生代区域,并以此作为最大新生代区域数。暂停时间上限与“暂停时间”点的间隔用于老年代区域的回收。
最大新生代区域数的设置发生在并发标记结束之后。
GC的切换
通常选择部分新生代GC。
只有使用完全新生代GC效率(需要根据历史数据进行预测)更高时才会切换为完全新生代GC。
切换的时间点也是并发标记结束之后。
GC的执行时机
新生代区域数达到上限时,会触发转移的执行。
通过调节最大新生代区域数,可以控制转移执行时机。
开始执行并发标记的条件 (转移完成后)
1、不在并发标记执行过程中
2、并发标记的结果已被上次转移使用完
3、已经使用了一定量的堆内存(默认45%)
4、相比上次转移,堆内存的使用量有所增加