Java Hotspot G1 GC
Garbage-First(G1) 垃圾收集器(Garbage Collector) 针对具有大量内存的多处理器机器。它试图以高概率满足垃圾收集暂停时间目标,同时在几乎不需要配置的情况下实现高吞吐量。G1 旨在使用当前目标应用程序和环境提供延迟和吞吐量之间的最佳平衡。
通过 -XX:+UseG1GC
参数来启用,作为体验版随着JDK 6u14版本面世,在JDK 7u4版本发行时被正式推出,在JDK 9中,G1被提议设置为默认垃圾收集器(JEP 248)。
JVM 提供的年轻代回收算法属于复制算法,CMS、G1,ZGC属于标记清除算法。
G1 是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。它是专门针对以下应用场景设计的:
- 像 CMS 收集器一样,能与应用程序线程并发执行。
- 整理空闲空间更快。
- 需要 GC 停顿时间更好预测。
- 不希望牺牲大量的吞吐性能。
- 不需要更大的 Java Heap。
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
- G1 是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。
- G1 的 Stop The World(STW) 更可控,G1 在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
G1 中重要概念
Region
传统的GC收集器将连续的内存空间划分为新生代、老年代和永久代(JDK 8去除了永久代(Permanent),引入了元空间Metaspace),这种划分的特点是各代的存储地址(逻辑地址)是连续的。如下图所示:
而 G1 的各代存储地址是不连续的,每一代都使用了n个不连续的大小相同的Region,每个Region占有一块连续的虚拟内存地址。如下图所示:
在上图中,我们注意到还有一些Region标明了H,它代表Humongous,这表示这些Region存储的是巨大对象(humongous object,H-obj),即大小大于等于region一半的对象。H-obj有如下几个特征:
- H-obj直接分配到了old gen,防止了反复拷贝移动。
- H-obj在global concurrent marking阶段的cleanup 和 full GC阶段回收。
- 在分配H-obj之前先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 full GC。
为了减少连续H-objs分配对GC的影响,需要把大对象变为普通的对象,建议增大Region size。
一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定。
通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。
垃圾回收周期
在高层次上,G1 收集器在两个阶段之间交替。Young-only 阶段包含垃圾收集,这些垃圾收集会逐渐用老年代中的对象填满当前可用的内存。空间回收阶段是 G1 除了处理年轻代外,还逐步回收老年代中的空间。然后循环以仅年轻阶段重新开始。
图 9-2 给出了关于这个循环的概述,并举例说明了可能发生的垃圾收集暂停序列:
以下列表详细描述了 G1 垃圾收集周期的阶段、它们的暂停和阶段之间的转换:
- Young-only 阶段:这个阶段从一些将对象提升到老年代的年轻集合开始。当老年代占用率达到某个阈值,即 Initiating Heap Occupancy 阈值时,young-only 阶段和空间回收阶段之间的转换开始。此时,G1 会安排 Initial Mark young-only 回收,而不是常规的 young-only 回收。
- Initial Mark :这种类型的收集除了执行常规的仅年轻收集之外,还会启动标记过程。并发标记确定老年代区域中所有当前可到达(活动)的对象,以保留用于下一个空间回收阶段。虽然标记尚未完全完成,但可能会发生常规的年轻收集。标记结束时有两个特殊的停顿:Remark 和 Cleanup。
- Remark:此暂停完成标记本身,并执行全局引用处理和类卸载。在 Remark 和 Cleanup G1 之间同时计算活动信息的摘要,这些信息将最终确定并在 Cleanup 暂停中用于更新内部数据结构。
- Cleanup:这个暂停也回收完全空的区域,并确定空间回收阶段是否会真正跟随。如果随后是空间回收阶段,则 Young-only 阶段以单个 Young-only 收集结束。
- 空间回收阶段:该阶段由多个混合集合组成,除了年轻代区域外,还撤离老年代区域集的活动对象。当 G1 确定撤出更多的老年代区域不会产生足够的可用空间值得努力时,空间回收阶段就结束了。
在空间回收之后,收集周期以另一个年轻阶段重新开始。作为备份,如果应用程序在收集活动信息时内存不足,G1 会像其他收集器一样执行就地停止世界全堆压缩(Full GC)。
G1 步骤
如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:
- 初始标记 Initial Making
- 并发标记 Concurrent Marking
- 最终标记 Final Marking:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
- 筛选回收 Live Data Counting and Evacuation:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
具备如下特点:
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
- 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。
与其他收集器比较
这是G1和其他收集器之间主要区别的总结:
- Parallel GC只能作为一个整体来压缩和回收老年代的空间。G1 将这项工作逐步分布在多个更短的集合中。这大大缩短了暂停时间,但可能会降低吞吐量。
- 与 CMS 类似,G1 并发执行部分老年代空间回收。但是,CMS 无法对老年代堆进行碎片整理,最终会遇到长时间的 Full GC。
- G1 可能表现出比其他收集器更高的开销,由于其并发性而影响吞吐量。
由于它的工作方式,G1 有一些独特的机制来提高垃圾收集效率:
- G1 可以在任何收集期间回收一些完全空的、大面积的老年代。这可以避免许多其他不必要的垃圾收集,无需太多努力即可释放大量空间。
- G1 可以选择同时尝试对 Java 堆上的重复字符串进行重复数据删除。
从老年代回收空的大对象总是启用的。您可以使用选项禁用此功能-XX:-G1EagerReclaimHumongousObjects。默认情况下禁用字符串重复数据删除。您可以使用选项启用它-XX:+G1EnableStringDeduplication。