G1垃圾收集器详细介绍

1、G1垃圾收集器介绍

G1垃圾收集器针对具有大量内存的多处理器机器。它试图以很高的概率满足GC停顿时间目标,同时实现高吞吐量且几乎不需要配置。G1旨在在延迟和吞吐量之间提供最佳平衡,应用场景包括如下环境特征:

  • 堆大小可达10 GB或更大,超过50%的Java堆占用实时数据。
  • 随着时间的推移,对象分配速度和晋升(从新生代到老年代的晋升)速度会发生显著变化。
  • 堆中大量的碎片。
  • 可预测的时间停顿目标不超过几百毫秒,避免长时间垃圾收集停顿。

G1取了CMS,G1也是默认的收集器(JVM9、JVM10)。

G1收集器有很高的性能,并尝试通过以下几节所述的几种方式来满足停顿时间的目标。

2、启用G1收集器

G1是默认收集器,因此通常不需要执行任何其他操作。您可以通过在命令行上提供-XX:+ UseG1GC来显式启用它。

3、基本概念

G1是分代的、增量的、并行、大部分、并发的、stop-the-word、以及疏散(将活着的对象从一个区域(youngor young + old)拷贝到另一个区域)的垃圾收集器,用于监视每个stop-the-word停顿的停顿时间目标。与其他收集器类似,G1将堆分成(虚拟)新生代和老年代。空间回收的主要集中在年轻代,因为这样做最有效率,在老年代偶尔会有空间回收。

一些操作总是在stop-the-word停顿中执行以提高吞吐量。其他需要更多时间停止应用程序的操作(例如全局标记等全堆操作)将与应用程序同时并行并发执行。为了让空间回收stop-the-word停顿短,G1逐步地并行执行空间回收。G1通过跟踪以前的应用程序行为信息和垃圾收集停顿的信息来建立相关成本的模型,以此实现可预测性。它使用这些信息来调整在停顿中完成的工作。例如,G1首先在最有效率的区域回收空间(即大部分填充垃圾的区域,因此是名称)。

G1主要通过疏散回收空间:回收时,在选定的内存区域内发现的活动对象被复制到新的内存区域,在此过程中压缩它们。疏散完成后,由先前活动对象占据的空间被重新用于应用程序的分配。

垃圾收集器不是实时收集器。 它试图在较长的时间内,以很高的概率满足设定的停顿时间的目标,但对于给定的停顿,并不总是具有绝对的确定性。

3.1、堆布局

G1将堆分成一组相同大小的region,每个region占有一个连续的虚拟内存地址,如图1所示。 region是内存分配和内存回收的基本单位。 在任何给定的时间,这些region中的每一个都可以是空的(浅灰色),或者已经分配给特定的年轻代或老年代。 当内存请求出现时,内存管理器会拿出空闲区域。 内存管理器将它们分配给某一代,然后将它们作为空闲空间返回给应用程序,在该空间中,它可以分配自身。

图1-G1堆内存布局

[图1-G1堆内存布局]

年轻代包含eden区(红色)和幸存区(包含“S”的红色)。 这些region提供与其他收集器中相应的连续空间相同的功能,区别在于,在G1中,这些region通常在内存中以非连续模式布局。老region(浅蓝色)组成了老年代。对于跨越多个区域的物体,老年代region可能会变得很大(包含“H”的浅蓝色)。

应用程序总是将内存分配到新生代,即eden region,但是除了那些被直接分配到老年代的大对象。

G1 GC停顿可以回收整个新生代的空间,并且在任何收集停顿时,任何附加的老年代region都可以回收。停顿期间,G1将此收集集合中的对象复制到堆中的一个或多个不同region。对象的目的region域取决于该对象的源region:整个新生代被复制到幸存者或老年代region,老年代region的对象复制到其他不同老年代region。

3.2、GC周期

在较高的水平上,G1收集器在两个阶段之间交替。 young-only阶段包含垃圾收集,这些垃圾收集逐渐用老年代的对象来填充当前可用的内存。space-reclamation阶段是G1除了处理年轻一代之外,逐步回收老一代的空间。然后,循环以young-only阶段重新开始。

图2以一个可能发生的垃圾收集停顿序列为例说明了这个周期。

img

[图2垃圾收集周期概述]

以下列表详细介绍了G1垃圾回收周期的这两个阶段,以及它们的停顿和转换过程:

1、young-only阶段:这一阶段从对象晋升到老年代的收集开始。当老年代占用率达到某一阈值(Initiating Heap Occupancy threshold)时,young-only阶段和space-reclamation阶段之间的转换就开始了。在这个时候,G1安排了一个初始标记的young-only收集,而不是一个普通的young-only收集。

  • 初始标记:除了执行常规的young-only的收集之外,这种类型的收集开始标记过程。并发标记确定老年代region中的所有当前可到达(实时)对象将保留到以下space-reclamation阶段。标记尚未完全结束时,可能会发生常规新生代收集。标记结束了两个特殊的stop-the-word停顿:重新标注和清理。
  • 重新标记:此停顿完成标记本身,并执行全局引用处理和类卸载。在重新标记和清理阶段之间G1并发计算对象活跃度概要信息,并将在清理停顿中用于更新内部数据结构。
  • 清理:此停顿也回收完全空白的区域,并确定space-reclamation阶段是否会实际执行。如果有space-reclamation阶段,那么young-only阶段完成一次young-only收集。

2、space-reclamation阶段:这一阶段由多个混合收集组成,除了新生代region之外,还会疏散老年代region存活对象。当G1确定疏散更多老一代的地区不会产生足够的可用空间时,space-reclamation阶段结束。

在space-reclamation之后,收集周期将以另一个young-only阶段重新开始。 作为备选,如果应用程序在收集对象存活信息时内存溢出,G1像其他收集器一样就地执行stop-the-word全局堆压缩(Full GC)。

4、G1内部

本节介绍G1 GC的一些重要细节。

4.1、确定Initiating HeapOccupancy

The Initiating Heap Occupancy Percent(IHOP)是触发初始标记收集的阈值,它被定义为老年代大小的百分比。

默认情况下,G1通过观察标记周期中标记需要多长时间以及老年代通常分配多少内存来自动确定最佳IHOP。这个功能称为Adaptive IHOP。如果此功能处于活动状态,在没有足够的观察值来很好地预测Initiating Heap Occupancy阈值的情况下,选项-XX:InitiatingHeapOccupancyPercent会以当前老年代的大小的百分比来确定初始值。 使用选项-XX:-G1UseAdaptiveIHOP关闭G1的这种行为。 在这种情况下,-XX:InitiatingHeapOccupancyPercent的值始终确定此阈值。

在内部,AdaptiveIHOP尝试设置Initiating Heap Occupancy,以便在老年代占有率处于当前最大老年代大小减去作为额外缓冲区的-XX:G1HeapReservePercent值时,开始空间回收阶段的第一个混合GC。

4.2、标记

G1标记使用称为 Snapshot-At-The-Beginning (SATB)的算法。 它在初始标记停顿时获取堆的虚拟快照,当标记开始时处于活动状态的所有对象,在标记剩余部分也被认为是活动的对象。这意味着标记期间变为死亡(无法访问)的对象,对space-reclamation阶段仍然被认为是存活的(有一些例外)。与其他收集器相比,这可能会导致一些额外的内存被错误保留。但是,在重新标记停顿期间,SATB可能会提供更好的延迟。在该标记过程中,过于保守考虑的存活对象,将在下一次标记过程中回收。

4.3、在堆内存紧张下的行为

当应用程序保持如此多的内存,疏散过程无法找到足够的空间进行复制时,会发生疏散失败。疏散失败意味着G1试图通过以下方式来完成当前GC,保留任何已经移动到新位置的对象,不复制任何尚未移动的对象,只调整对象之间的引用。疏散失败可能会带来一些额外开销,但通常应该像其他年轻代收集一样快。在疏散失败的GC之后,G1将照常恢复应用程序,无需任何其他措施。G1假定疏散失败发生在GC结束附近; 也就是说,大多数对象已经移动并且有足够的空间继续运行应用程序,直到标记完成并开始space-reclamation。

如果这个假设不成立,那么G1最终将安排一个fullGC。 这种类型的收集就地执行整个堆的压缩。 这可能非常缓慢。

4.4、大对象

大对象是大于或等于半个region大小的对象。 除非使用-XX:G1HeapRegionSize选项进行设置,否则当前region的尺寸按照人体工程学设计确定,参考“G1 GC人体工程学默认值”章节所述。

这些大对象有时以特殊的方式进行处理:

  • 每一个大对象都被分配为老年代的一系列连续region。对象的开始位置始终位于该序列中第一个region的开始位置。该序列中最后一个region的剩余空间将丢失,直到整个对象被回收。
  • 一般来说,只有在清理停顿期间的标记结束时,或者在Full GC期间,如果大对象变得无法到达,则可以被回收。但是,对于原始类型数组的大对象(例如bool,各种整数和浮点值)有一个特殊规定。如果G1在任何类型的GC停顿时都没有被任何对象引用,那么G1会尝试回收大对象。此行为默认启用,但可以使用选项-XX:G1EagerReclaimHumongousObjects将其禁用。
  • 大对象的分配可能会导致GC停顿过早发生。 G1会在每个大对象分配中检查InitiatingHeap Occupancy阈值,如果当前占用率超过该阈值,可能会立即强制新生代收集初始标记。
  • 大对象不会移动,即使在fullGC中也不会移动。 这可能会导致过早执行缓慢的fullGC或意外的内存溢出情况,尽管region空间碎片留下大量剩余空间。
4.5、young-only阶段代大小设置

在young-only阶段,要收集的region集合(收集集合)只包括新生代region。G1一直在young-only 收集结束时设置新生代大小。这样,G1根据实际暂停时间的长期观察值,就可以满足使用-XX:MaxGCPauseTimeMillis和-XX:PauseTimeIntervalMillis设置的停顿时间目标。它考虑了新生代同样规模的疏散需要多长时间。这包括诸如在收集期间需要复制多少个对象以及这些对象之间是如何相互关联的信息。

如果没有其他约束,则G1适应性地将年轻代大小设定为-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent确定的值以满足停顿时间。

4.6、Space-reclamation阶段代大小设置

在space-reclamation阶段,G1试图在单个垃圾收集停顿中最大回收化老年代空间。新生代的大小设置为允许的最小值,通常由-XX:G1NeSizeSizePercent确定,并且任何老年代region回收空间都会被添加,直到G1确定添加更多region将超过停顿时间的目标。在特定的GC停顿中,G1按其回收效率、剩余可用时间顺序添加老年代region,以获得最终收集集合。

要收集的潜在候选老年代region(收集集合候选region)的低端数量除以由-XX:G1MixedGCCountTarget确定的space-reclamation阶段的长度,得到商X,每次GC所采用的老年代region数量由商X确定下界。收集集合候选region:在本阶段开始时所占用的(小于-XX:G1MixedGCLiveThresholdPercent)老年代region

当收集组候选region中可回收的剩余空间量小于-XX:G1HeapWastePercent设置的百分比时,space-reclamation阶段结束。

5、G1 GC的人机工程学默认值

本主题概述了G1的最重要的参数及其默认值。他们给出了对预期行为和资源使用情况粗略概述,没有任何其他选项。

img
**

注意:意味着实际的值是由人体工程学决定的,这取决于环境。

6、与其他收集器对比

这是对G1和其他收集器之间主要区别的总结:

  • Parallel GC在老年代中全部压缩和回收空间。G1逐渐将这个工作分布在多个更短的收集中。这显著缩短了暂停时间,但可能会降低吞吐量。
  • 与CMS相似,G1并发执行部分老年代space-reclamation。然而,CMS不能将老年代堆碎片整理出来,最终会运行长时间的full GC。
  • G1的开销可能比其他收集器高,因为它的并发特性影响了吞吐量。

由于它的工作原理,G1有一些独特的机制来提高垃圾收集效率:

  • 在任何收集过程中,G1都可以回收老年代的一些完全空的、大的区域。这可以避免许多不必要的垃圾收集,在不费多大力气的情况下释放大量的空间。
  • G1可以选择性地尝试同时在Java堆上删除重复的字符串。

从老年代中回收空的大型对象总是启用的。您可以使用选项-XX:-G1EagerReclaimHumongousObjects来禁用该特性。在默认情况下,字符串重复删除是禁用的。你可以使用选项-XX:+ G1EnableStringDeduplication来启用它。

由于水平有限,翻译的不好,欢迎批评指正。

参考地址

如果大家喜欢我的文章,可以关注个人订阅号。欢迎随时留言、交流。如果想加入微信群的话一起讨论的话,请加管理员简栈文化-小助手(lastpass4u),他会拉你们进群。

简栈文化服务订阅号