分区算法的堆结构
G1 GC 的堆结构:
G1 的老年代和年轻代不再是一块连续的空间,整个堆被划分成若干个大小相同的 Region,也就是区。Region 的类型有 Eden、Survivor、Old、Humongous 四种,而且每个 Region 都可以单独进行管理。
Humongous 是用来存放大对象的,如果一个对象的大小大于一个 Region 的 50%(默认值),那么我们就认为这个对象是一个大对象。为了防止大对象的频繁拷贝,我们可以将大对象直接放到 Humongous 中。
实际上,分区垃圾回收算法最大的特点是维护跨分区引用,这也是它实现起来最难的地方。
写屏障
在删除引用的时候进行维护的屏障叫做 deletion barrier。删除时,被引用的对象标记为活跃对象。
这种做法的特点是,在 GC 标记开始的一瞬间,活跃的对象无论在标记期间发生怎样的变化,都会被认为是活跃的对象。在并发标记阶段,即便对象的全部引用被删除,也会被当做活跃对象来处理。就好像在 GC 开始的瞬间,内存管理器为所有活跃对象做了一个快照一样,所以人们给了这种技术一个很形象的名字:开始时快照(Snapshot At The Beginning,SATB)。
GC 开发者将“C 对象标记为灰色”这件事情往后推迟了。业务线程只需要把 C 对象记录到一个本地队列中就可以了。每个业务线程都有一个这样的线程本地队列,它的名字是 SATB 队列。当业务线程发现对象 C 的引用被删除之后,直接将 C 放到 SATB 队列中,并不去做标记,真正做标记的工作交给 GC 线程去做,这样就减少了写屏障的开销。
每个线程有自己的本地 SATB 队列,当本地队列满了之后,就把它交给 SATB 队列集合,然后再领取一个空队列当做线程的本地 SATB 队列。GC 线程则会将 SATB 队列集合中的对象标记为灰色,至于什么时候标记,并不需要业务线程关心。
垃圾回收模式
G1 的垃圾回收模式有两种:分别是 young GC 和 mixed GC。
- young GC:只回收年轻代的 Region。
- mixed GC:回收全部的年轻代 Region,并回收部分老年代的 Region。
我们把 mixed GC 中选取的老年代对象 Region 的集合称之为回收集合(Collection Set,CSet)。CSet 的选取要素有以下两点:
- 该 Region 的垃圾占比。垃圾占比越高的 Region,被放入 CSet 的优先级就越高,这就是垃圾优先策略(Garbage First),也是 G1 GC 名称的由来。
- 建议的暂停时间。建议的暂停时间由 -XX:MaxGCPauseMillis 指定,G1 会根据这个值来选择合适数量的老年代 Region。
MaxGCPauseMillis 默认是 200ms,一般不需要进行调整,如果需要停顿时间更短可以对它进行设置,不过需要注意的是,MaxGCPauseMillis 设置的越小,选取的老年代 Region 就会越少,如果 GC 压力居高不下,就会触发 G1 的 Full GC。
维护跨区引用
分区回收算法为每个 Region 都引入了记录集(Remembered Set,RSet),每个 Region 都有自己的专属 RSet。
和 Card table 不同的是,RSet 记录谁引用了我,这种记录集被人们称为 point-in 型的,而 Card table 则记录我引用了谁,这种记录集被称为 point-out 型。
RSet 需要维护的引用关系只有两种,非 CSet 老年代 Region 到年轻代 Region 的引用,和非 CSet 老年代 Region 到 CSet 老年代 Region 的引用。
根据另一个 Region 对这个 Region 的引用数量,可以分为少、中、多三种情况。针对这三种情况,RSet 准备了三种不同的数据结构来应对,分别是稀疏表、细粒度表和粗粒度表。三种表之间的关系是不断粗化的,如下图所示:
- 稀疏表是一个哈希表,当 Region A 对 Region B 的引用很少时,就可以将相关的 card 放到稀疏表里;
- 细粒度表则是一个真正的 card table,当 Region 之间的引用比较多时,就可以直接使用位图来代替哈希表,因为这能加快查找的速度(使用位操作代替哈希表的查找);
- 粗粒度表则是一个区的位图,因为相对来说,区是比较少的,所以粗粒度表的大小也很小。当 Region A 对 Region B 的引用非常多时,就不用再使用 card table 来进行管理了,在回收 Region B 时,直接将 Region A 的全部对象都遍历一次就可以了。
G1 常用参数
大多数情况下,不需要再调整。
此文章为7月Day22学习笔记,内容来源于极客时间《编程高手必学的内存知识》