G1垃圾回收器

简介

G1 收集器是一款主要面向服务端应用的垃圾收集器,特点是以极高概率满足GC停顿时间的要求(可预测的停顿),同时具备高吞吐性能。

堆内存划分

虽然 G1 收集器也遵循分代收集理论,但其堆内存的布局与其他收集器有非常明显的差异:

  • G1 不再坚持固定大小和固定数量的分代区域划分,而是把连续的 Java 堆划分为约2048个个大小相等的分区(Region),每个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。
  • Region 中还有一类特殊的 Humongous 区域,专门来存储大对象(大小超过一个 Region 容量的一半);而对于超过整个 Region 的超大对象,将会被存在 N 个连续的 Humongous Region 中(G1 的大多数行为都把 Humongous Region 作为老年代的一部分看待)。
  • 从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden区和Survivor区。但从堆的结构上看,它不要求整个Eden区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。(一个region有可能属于Eden、Survivor、Old或者Humongous区域,但是一个region只可能属于一个角色)

G1 收集器的堆内存划分如图所示:

G1垃圾回收器_第1张图片

停顿时间模型

停顿时间模型(Pause Prediction Model):指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过 N 毫秒。

G1 收集器之所以能建立可预测的停顿时间模型,是因为它将 Region 作为单次回收的最小单元(每次收集到的内存空间都是 Region 大小的整数倍),这样可以有计划地避免整个 Java 堆进行全区域垃圾收集。更具体的处理思路:让 G1 收集器去跟踪各个 Region 中的垃圾堆积的“价值”大小,然后在后台维护一个优先级列表,每次根据用户设定的收集停顿时间,优先处理回收价值收益最大的那些 Region

“价值”的衡量指标是:基于平均衰减的数学模型得出的每次回收所获得的空间大小以及回收所需时间的经验值。

Mix GC

G1 收集器之前的其他所有收集器(包括 CMS 收集器),垃圾收集的目标范围要么是整个新生代(Minor GC),或者整个老年代(Major GC),抑或整个 Java 堆(Full GC)。
而 G1 跳出了这个樊笼:它可以面向堆内存中任何部分来组成回收集(Collection Set,一般称 CSet)进行回收。衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多、回收收益最大。这就是 G1 收集器的 Mixed GC 模式。

垃圾收集过程

它的运作过程大致可分为以下四个步骤:

  1. 初始标记
    仅标记 GC Roots 能直接关联到的对象。
    修改 TAMS 指针的值,使得下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。
    特点:需要停顿用户线程,但耗时很短,且是借用 Minor GC 时同步完成的。

TAMS:Top at Mark Start,Region 中的指针,用于并发标记时为对象分配内存空间。

  1. 并发标记
    从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象。
    此外,扫描完成后,还需要重新处理 STAB 记录下的在并发时有引用变动的对象。
    特点:耗时较长,可与用户程序并发执行

STAB: Snapshot At The Begining,原始快照

  1. 最终标记
    处理并发标记结束后仍遗留下来的最后少量的 STAB 记录。
    特点:需要暂停用户线程(时间较短)。

  2. 筛选回收
    更新 Region 统计数据,对各个 Region 的回收价值和成本进行排序。
    根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。
    特点:由于涉及存活对象的移动,需要暂停用户线程。

收集算法

整体:“标记整理算法”
局部:“标记复制算法”
G1 的这两种算法使其在运作期间不会产生内存空间碎片,垃圾收集完成后能提供规整的可用内存。而且这样有利于程序长时间运行(大对象分配内存时不容易因无法找到连续内存空间而提前触发下一次收集)。

年轻代GC过程

G1垃圾回收器_第2张图片

老年代GC过程(并发标记周期)

  1. 初始标记:stop-the-world,它伴随着一次普通的 Young GC 发生,然后对 Survivor 区(root region)进行标记,因为该区可能存在对老年代的引用。
  2. 扫描根引用区:因为先进行了一次 YGC,所以当前年轻代只有 Survivor 区有存活对象,它被称为根引用区。扫描 Survivor 到老年代的引用,该阶段必须在下一次 Young GC 发生前结束。
  3. 并发标记:寻找整个堆的存活对象,该阶段可以被 Young GC 中断。
  4. 重新标记:stop-the-world,完成最后的存活对象标记。使用了比 CMS 收集器更加高效的 snapshot-at-the-beginning (SATB) 算法。(Oracle 的资料显示,这个阶段会回收完全空闲的区块。)
  5. 清理:清理阶段真正回收的内存很少。

到这里,G1 的一个并发周期就算结束了,其实就是主要完成了垃圾定位的工作,定位出了哪些分区是垃圾最多的。因为整堆一般比较大,所以这个周期应该会比较长,中间可能会被多次 stop-the-world 的 Young GC 打断。

混合垃圾回收周期

并发周期结束后是混合垃圾回收周期,不仅进行年轻代垃圾收集,而且回收之前标记出来的老年代的垃圾最多的部分区块。
混合垃圾回收周期会持续进行,直到几乎所有的被标记出来的分区(垃圾占比大的分区)都得到回收,然后恢复到常规的年轻代垃圾收集,最终再次启动并发周期。

一些细节问题

  1. 并发标记
    并发标记阶段如何保证收集线程与用户线程互不干扰地运行呢?
    G1 收集器则是通过原始快照(STAB)算法实现的。

此外,由于并发标记时用户线程仍在继续执行,肯定会持续创建新对象。
G1 为每个 Region 设计了两个名为 TAMS(Top at Mark Start)的指针,把 Region 中的一部分空间划分出来用于并发回收过程中的新对象分配(默认都是存活的,不纳入回收范围)。

需要注意的是:如果内存回收速度赶不上内存分配的速度,G1 收集器也要被迫冻结用户线程执行,导致 Full GC 而产生长时间“Stop The World”。

  1. 可靠的停顿模型
    G1 收集器的停顿模型是以衰减均值(Decaying Average)为理论基础来实现的:垃圾收集过程中,G1 收集器会根据每个 Region 的回收耗时、记忆集中的脏卡数量等,分析得出平均值、标准偏差等。
    “衰减平均值”比普通的平均值更能准确地代表“最近的”平均状态,通过这些信息预测现在开始回收的话,由哪些 Region 组成回收集才能在不超期望停顿时间的约束下获得最高收益。

  2. 相关虚拟机参数设置

# 使用 G1 收集器
-XX:+UseG1GC

# 设置 Region 大小(范围 1~32M,且为 2 的 N 次幂)
-XX:G1HeapRegionSize

# 最大收集停顿时间(默认 200 毫秒)
-XX:MaxGCPauseMillis

你可能感兴趣的:(Java,java)