Garbage-First (G1) 收集器

一、G1收集器简述

Garbage First 简称(G1) 收集器是一款面向服务端应用的垃圾收集器,从 JDK9 开始也是服务端模式下的默认垃圾收集器。它可以说是垃圾收集器发展中一个重要的里程碑,因为它提出了面向局部收集的设计思想基于Region的内存布局形式。在JDK 7刚刚确立项目目标时,其实就已经把G1作为一个重要的进化特征,直到JDK7 Update 4,Oracle才认为其够商业的使用,到JDK8Update 40的时候,G1提供了并发类卸载功能,完成了计划的全部功能。此后的JDK版本才被Oracle官方称为“全功能的垃圾收集器”(Fully-Featured Garbage Collector) 。

二、垃圾收集器的评估

HotSpot的垃圾收集器发展了近二十年多年,目前已经相当成熟了,不过它们距离“完美”还是很遥远。怎样的收集器才算是“完美”呢?怎么客观的描述这个这个事情呢? 衡量垃圾收集器的三项最重要的指标是:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency),三者共同构成了一个“不可能三角”。想要实现三者都优越是及其具有挑战性的,一款优秀的收集器通常最多可以同时达到其中的两项。

三、G1的详解

3.1 G1的堆结构

在“为什么会有分代收集器算法?”一章中,我们知道把对象分代以后,可以大大减轻垃圾回收的压力,进而就减少了停顿时长。那么我们讲其分成更多的区域是不是更好呢。基于其思路发展出了分区回收算法。

 

1. G1分区的具体的是怎么样的呢?

G1的老年代和年轻代不再是一块连续的空间,整个堆被划分为若干个相同的Region,也就是区。Region的类型有Eden、Survivor、Old、Humongous 四种,而且每个 Region 都可以单独进行管理。而 Eden、Survivor、Old 三种区域和我们前面课程中介绍的 Eden 分区、Survivor 分区以及老年代的作用是类似的。也就是说,对象会在 Eden Regions 中分配,当进行年轻代 GC 时,会将活跃对象拷贝到 Survivor Regions;当对象年龄超过晋升阈值时,就把活跃对象复制进 Old Regions。G1收集器之所以能建立可预测的停顿时间模型, 是因为它将Region作为单次回收的最小单元, 即每次收集到的内存空间都是Region大小的整数倍, 这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。 更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小, 价值即回收所获得的空间大小以及回收所需时间的经验值, 然后在后台维护一个优先级列表, 每次根据用户设定允许的收集停顿时间(使用参数-XX: MaxGCPauseMillis指定, 默认值是200毫秒) , 优先处理回收价值收益最大的那些Region, 这也就是“Garbage First”名字的由来。这种使用Region划分内存空间, 以及具有优先级的区域回收方式, 保证了G1收集器在有限的时间内获取尽可能高的收集效率。

2. 大对象区(Humongous )

Humongous 是用来存放大对象的,如歌一个对象的大小大于Region的50%(默认值),那么这个对比会直接放入到Humongous区中,因为为了防止大对象的频繁拷贝。而对于那些超过了整个Region容量的超级大对象, 将会被存放在N个连续的Humongous Region之中, G1的大多数行为都把Humongous Region作为老年代 的一部分来进行看待。

3.2 G1整体流程

以下为G1收集器的示意图:

如果我们不去计算用户线程运行过程中的动作(如使用写屏障维护记忆集的操作) ,G1收集器的运作过程大致可划分为以下四个步骤:

1. 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象, 并且修改TAMS(Top at Mark Start) 指针的值, 让下一阶段用户线程并发运行时, 能正确地在可用的Region中分配新对象。 这个阶段需要停顿线程, 但耗时很短, 而且是借用进行Minor GC的时候同步完成的, 所以G1收集器在这个阶段实际并没有额外的停顿。

2. 并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB(Snapshot At The Beginning)记录下的在并发时有引用变动的对象。

3. 最终标记(Final Marking):对用户线程做另一个短暂的暂停, 用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。

4. 筛选回收(Live Data Counting and Evacuation):更新统计Region数据,对Region进行回收价值和成本排序并且根据用户设置的停顿时间 (-XX:MaxGCPauseMillis) 来制定回收计划,该值默认200ms。然后可以任意选择多个Region构成回收集,把最终要回收的那一部分Region的存活对象复制到空的Region中,再清除原来的Region。 该对象涉及对象的移动,因此需要停止用户线程。

TAMS(Top at Mark Start)指针 名词解释:Region中的一部分空间划分出来用于并发回收过程中的新对象分配, 并发回收时新分配的对象地址都必须要在这两个指针位置以上。

3.3 G1的写屏障

我们将java堆划分成多个region后,看起来简单但是从存在较多问题。维护跨分区引用,其中的关键就是写屏障。可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面, 在引用对象赋值时会产生一个环形(Around) 通知, 供程序执行额外的动作, 也就是说赋值的前后都在写屏障的覆盖范畴内。 在赋值前的部分的写屏障叫作写前屏障(Pre-Write Barrier) 在赋值后的则叫作写后屏障(Post-Write Barrier) 。 在 CMS 中,写屏障主要有两个作用:

1.在并发标记阶段解决活跃对象漏标问题;

2.在写屏障里使用 card table 维护跨代引用。

我们先来看第一个作用,也就是解决活跃对象漏标的问题。上图案例中,当对象 B 对 C 的引用关系消失以后,再将 C 标记为灰色,即便将来 A 对 C 的引用消失了,也会在当前 GC 周期内被视为活跃对象。也就是说,C 有可能变成浮动垃圾。我们把这种在删除引用的时候进行维护的屏障叫做 deletion barrier,G1 中采用的就是这种做法。

 

3.4 G1的开始时快照

上述做法的特点是,在 GC 标记开始的一瞬间,活跃的对象无论在标记期间发生怎样的变化,都会被认为是活跃的对象。当一个对象的全部引用被删除时,才会被当做垃圾。而如果使用我们前面讲到的 deletion barrier,在并发标记阶段,即便对象的全部引用被删除,也会被当做活跃对象来处理。就好像在 GC 开始的瞬间,内存管理器为所有活跃对象做了一个快照一样,所以人们给了这种技术一个很形象的名字:开始时快照(Snapshot At The Beginning,SATB)

当 B 对象对 C 对象的引用消失时,C 对象将会被标记为灰色。这个动作的效率是比较低的,如果都放在写屏障中做,会极大地影响程序性能。为了解决这个问题,GC 开发者将“C 对象标记为灰色”这件事情往后推迟了。业务线程只需要把 C 对象记录到一个本地队列中就可以了。每个业务线程都有一个这样的线程本地队列,它的名字是 SATB 队列。当业务线程发现对象 C 的引用被删除之后,直接将 C 放到 SATB 队列中,并不去做标记,真正做标记的工作交给 GC 线程去做,这样就减少了写屏障的开销。

当本地队列满了之后,就把它交给 SATB 队列集合,然后再领取一个空队列当做线程的本地 SATB 队列。GC 线程则会将 SATB 队列集合中的对象标记为灰色,至于什么时候标记,并不需要业务线程关心。

3.5 垃圾回收模式

G1 的垃圾回收模式有两种:分别是 young GC 和 mixed GC。

young GC:只回收年轻代的 Region。mixed GC:回收全部的年轻代 Region,并回收部分老年代的 Region。

上述两种都会回收全部的年轻代,mixed 回收的老年代 Region 是需要进行决策的。

那么决定老年代 Region 是否被回收的因素具体有哪些呢?我们把 mixed GC 中选取的老年代对象 Region 的集合称之为回收集合(Collection Set,CSet)。CSet 的选取要素有以下两点:

1.该 Region 的垃圾占比。垃圾占比越高的 Region,加入CSet 的优先级就越高,这就是G1垃圾优先策略(Garbage First)名字的由来。

2.建议的暂停时间。建议的暂停时间由 -XX:MaxGCPauseMillis 指定,G1 会根据这个值来选择合适数量的老年代 Region,一般不需要进行调整。想要停顿时间短,可以对其进行设置,不过需要注意的是,MaxGCPauseMillis 越小,选取的老年代 Region 就会越少,如果 GC 压力居高不下,就会触发 G1 的 Full GC。触发 G1 的 Full GC 代价是很高的。如果程序会频繁触发 G1 的 Full GC,那么说明这个应用的 GC 参数配置是不合理的,理想情况下 G1 是没有 Full GC 的。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值