本节介绍垃圾优先 (G1) 垃圾收集器 (GC)。

垃圾优先GC简介

垃圾优先 (G1) 垃圾收集器针对具有大量内存的多处理器机器。它试图以高概率的满足垃圾收集暂停时间目标,同时在几乎不需要配置的情况下实现高吞吐量。G1 旨在使用当前的目标应用程序和环境在延迟和吞吐量之间提供最佳平衡,其功能包括:

  • 堆大小高达 10 GB 或更大,其中超过 50% 的 Java 堆被实时数据占用
  • 对象分配和提升的速率可能随时间显着变化
  • 堆中有大量碎片
  • 不超过几百毫秒的可预测暂停时间目标,避免长时间的垃圾收集暂停

G1 替换了并发标记扫描 (CMS) 收集器。它也是默认收集器。G1 收集器实现了高性能,并尝试通过以下部分中描述的几种方式来满足暂停时间目标。

启用 G1

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

基本概念

G1 是一个分代的、增量的、并行的、主要是并发的、stop-the-world 和 evacuating 垃圾收集器,它监视每个 stop-the-world 暂停中的暂停时间目标。与其他收集器类似,G1 将堆分成(虚拟)新生代和年老代。空间回收工作集中在最有效的年轻代上,偶尔在老年代进行空间回收。

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

G1 主要通过使用疏散来回收空间:在要收集的选定内存区域内找到的活动对象被复制到新的内存区域,并在此过程中压缩它们。疏散完成后,先前由活动对象占用的空间将重新用于应用程序分配。垃圾优先收集器不是实时收集器。它试图在更长的时间内以高概率满足设定的暂停时间目标,但对于给定的暂停并不总是绝对确定。

堆布局

G1 将堆划分为一组大小相等的堆区域,每个区域都是连续的虚拟内存范围,如图 9-1 所示。区域是内存分配和内存回收的单位。在任何给定的时间,这些区域中的每一个都可以是空的(浅灰色),或者分配给特定的代,年轻的或年老的。当内存请求到来时,内存管理器会分发空闲区域。内存管理器将它们分配给代,然后将它们作为可用空间返回给应用程序,应用程序可以在其中分配自己。
image.png
图 9-1 G1 垃圾收集器堆布局

年轻代包含Eden区域(红色)和幸存者区域(红色带“S”)。这些区域提供与其他收集器中的相应连续的堆空间相同的功能,不同之处在于在 G1 中,这些区域通常以非连续模式布置在内存中老区(浅蓝色)组成老年代。对于跨越多个区域的对象,老年代区域可能是巨大的(带有“H”的浅蓝色)。

应用程序总是分配到年轻代,即 eden 区域,除了巨大的对象,它们被直接分配为属于老年代。G1 垃圾收集暂停可以作为一个整体回收年轻代中的空间,以及任何收集暂停时任何额外的老年代区域集。在暂停期间,G1 将对象从该集合集中复制到堆中的一个或多个不同区域。对象的目标区域取决于该对象的源区域:整个年轻代被复制到幸存者区域或老区域,并且使用老化将对象从老区域复制到其他不同的老区域。

垃圾回收周期

在高层次上,G1 收集器在两个阶段之间交替。仅年轻阶段包含垃圾收集,这些垃圾收集逐渐用老年代中的对象填充当前可用内存空间回收阶段是 G1 除了处理年轻代之外,还逐步回收老年代中的空间然后循环以仅年轻阶段重新开始。图 9-2 给出了有关此循环的概述,并举例说明了可能发生的垃圾收集暂停序列:

image.png

图 9-2 垃圾回收周期概览

下面的列表详细描述了 G1 垃圾回收周期的各个阶段、它们的暂停以及各个阶段之间的转换:

  1. Young-only 阶段:这个阶段从一些年轻的集合开始,将对象提升到老年代。当老年代占用率达到某个阈值,即初始堆占用阈值时,年轻代和空间回收阶段之间的过渡开始。此时,G1 安排了 Initial Mark 仅年轻收集而不是常规的仅年轻收集。

    • 初始标记:除了执行常规的仅年轻收集之外,这种类型的收集还启动标记过程。并发标记决定了老年代区域中所有当前可访问的(活动的)对象,以便为接下来的空间回收阶段保留。虽然标记还没有完全完成,但可能会发生定期的年轻集合。标记以两个特殊的停顿结束:标记和清理。
    • 标记:此暂停完成标记本身,并执行全局引用处理和类卸载。在 Remark 和 Cleanup 之间 G1 会同时计算一个活动信息的摘要,该摘要将在 Cleanup 暂停中完成并用于更新内部数据结构。
    • 清理:此暂停还会回收完全空白的区域,并确定空间回收阶段是否真的会随之而来。如果接下来是空间回收阶段,则仅年轻阶段以单个仅年轻集合结束。
  2. 空间回收阶段:该阶段由多个混合集合组成,除了年轻代区域外,还清除老年代区域集的活动对象。当 G1 确定撤离更多的老年代区域不会产生足够的可用空间时,空间回收阶段结束。

空间回收后,收集周期从另一个仅年轻阶段重新开始。作为备份,如果应用程序在收集活动信息时内存不足,G1 会像其他收集器一样执行就地STW全堆压缩(Full GC)。

垃圾优先内部结构

本节描述了 Garbage-First (G1) 垃圾收集器的一些重要细节。

确定启动堆占用

起爆堆占有百分比(IHOP)是在该初始集合标记触发阈值,并将其定义为老一代大小的百分比。
默认情况下,G1 通过观察标记需要多长时间以及在标记周期中通常在老年代分配多少内存来自动确定最佳 IHOP。此功能称为自适应 IHOP。如果此功能处于活动状态,则该选项-XX:InitiatingHeapOccupancyPercent将初始值确定为当前老年代大小的百分比,只要没有足够的观察来对初始堆占用阈值进行良好的预测。使用选项关闭 G1 的这种行为-XX:-G1UseAdaptiveIHOP。在这种情况下,-XX:InitiatingHeapOccupancyPercent始终由 的值确定此阈值。在内部,自适应 IHOP 尝试设置初始堆占用率,以便当老年代占用率处于当前最大老年代大小减去-XX:G1HeapReservePercent作为额外缓冲区的值时,空间回收阶段的第一个混合垃圾收集开始。

打标

G1 标记使用称为Snapshot-At-The-Beginning (SATB) 的算法。它在初始标记暂停时拍摄堆的虚拟快照,此时所有在标记开始时处于活动状态的对象在标记的剩余时间内都被认为是活动的。这意味着在标记期间变为死亡(无法访问)的对象仍然被认为是活动的,以便进行空间回收(有一些例外)。与其他收集器相比,这可能会导致错误保留一些额外的内存。但是,SATB 可能会在 Remark 暂停期间提供更好的延迟。在该标记期间过于保守地考虑的活动对象将在下一次标记期间被回收。有关 标记问题的更多信息,请参阅垃圾优先垃圾收集器调优主题。

在非常紧的堆情况下的行为

当应用程序保持活动的内存如此之多以至于疏散找不到足够的空间来复制时,就会发生疏散失败。疏散失败意味着 G1 尝试通过将任何已经移动的对象保留在其新位置来完成当前的垃圾收集,并且不复制任何尚未移动的对象,仅调整对象之间的引用。疏散失败可能会产生一些额外的开销,但通常应该与其他年轻集合一样快。在这次垃圾回收并疏散失败后,G1 将照常恢复应用程序,不采取任何其他措施。G1 假设疏散失败发生在垃圾收集接近结束时;那是,如果这个假设不成立,那么 G1 最终会安排一个 Full GC。这种类型的集合执行整个堆的就地压缩。这可能非常慢。请参阅垃圾优先垃圾收集器调优以获取有关内存不足信号之前分配失败或 Full GC 问题的更多信息。

大的对象

巨大的物体是大于或等于半个区域大小的物体。当前区域大小是按照G1 GC的人体工程学默认值部分中所述的人体工程学确定的,除非使用该-XX:G1HeapRegionSize选项进行设置。
这些巨大的物体有时会被特殊对待:

  • 每个巨大的对象都被分配为老年代中的一系列连续区域。对象本身的起点始终位于该序列中第一个区域的起点。序列最后一个区域中的任何剩余空间都将丢失以进行分配,直到回收整个对象。
  • 通常,只有在 Cleanup 暂停期间标记结束时,或者在 Full GC 期间,如果它们变得无法访问,才能回收巨大的对象。但是,对于原始类型数组的庞大对象,例如 ,bool各种整数和浮点值,有一个特殊规定。如果在任何类型的垃圾收集暂停时没有被许多对象引用,G1 会机会性地尝试回收庞大的对象。默认情况下启用此行为,但您可以使用选项禁用它-XX:G1EagerReclaimHumongousObjects。
  • 大量对象的分配可能会导致垃圾收集暂停过早发生。G1 在每次巨大的对象分配时检查初始堆占用阈值,如果当前占用超过该阈值,则可能会立即强制进行初始标记年轻收集。
  • 庞大的对象永远不会移动,即使在 Full GC 期间也是如此。这可能会导致过早的缓慢 Full GC 或意外的内存不足情况,由于区域空间的碎片而留下大量可用空间。

  • 仅-年轻阶段生成尺寸

在 Young-only 阶段,要收集的区域集(收集集)仅由年轻代区域组成。G1 总是在仅年轻集合的末尾调整年轻代的大小。通过这种方式,G1 可以满足使用-XX:MaxGCPauseTimeMillis并-XX:PauseTimeIntervalMillis基于对实际暂停时间的长期观察而设定的暂停时间目标。它考虑了类似大小的年轻一代撤离所需的时间。这包括诸如在收集期间必须复制多少对象以及这些对象的互连程度等信息。
如果没有特别的限制,那么G1自适应大小的值之间的年轻一代大小-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent确定,以满足暂停时间。 有关如何修复长时间停顿的更多信息,请参阅垃圾优先垃圾收集器调优。

空间回收阶段生成大小

在空间回收阶段,G1 尝试最大化在一次垃圾回收暂停中在老年代回收的空间量。年轻代的大小设置为允许的最小值,通常由 决定-XX:G1NewSizePercent,并且任何老年代区域都会被添加以回收空间,直到 G1 确定添加更多区域将超过暂停时间目标。在特定的垃圾回收暂停中,G1 按照回收效率、最高的优先级和剩余可用时间的顺序添加老年代区域,以获得最终的回收集。
每次垃圾回收要占用的老年代区域的数量的下限是要收集的潜在候选老年代区域(收集集候选区域)的数量,除以由 确定的空间回收阶段的长度-XX:G1MixedGCCountTarget。收集集候选区域都是占有率低于-XX:G1MixedGCLiveThresholdPercent阶段开始时的所有老年代区域。
当收集集候选区域中可回收的剩余空间量小于由 设置的百分比时,阶段结束-XX:G1HeapWastePercent。
有关G1 将使用多少旧代区域以及如何避免长时间混合收集暂停的更多信息,请参阅垃圾优先垃圾收集器调优。

G1 GC 的人体工程学默认设置

本主题概述了特定于 G1 的最重要默认值及其默认值。他们使用 G1 粗略地概述了预期行为和资源使用情况,而没有任何其他选项。
表 9-1 G1 GC 人体工程学默认值

选项和默认值 描述
-XX:MaxGCPauseMillis=200 最大暂停时间的目标。
-XX:GCPauseTimeInterval= 最大暂停时间间隔的目标。默认情况下 G1 不设置任何目标,允许 G1 在极端情况下背靠背执行垃圾收集。
-XX:ParallelGCThreads= 垃圾收集暂停期间用于并行工作的最大线程数。这是从运行 VM 的计算机的可用线程数派生的,方法如下:如果进程可用的 CPU 线程数小于或等于 8,则使用它。否则,添加大于最终线程数的八分之五的线程。
-XX:ConcGCThreads= 用于并发工作的最大线程数。默认情况下,该值-XX:ParallelGCThreads除以 4。
-XX:+G1UseAdaptiveIHOP
-XX:InitiatingHeapOccupancyPercent=45
控制初始堆占用率的默认值表示该值的自适应确定已打开,并且在前几个收集周期 G1 将使用 45% 的老年代占用率作为标记开始阈值。
-XX:G1HeapRegionSize= 基于初始和最大堆大小的堆区域大小的集合。因此该堆包含大约 2048 个堆区域。堆区域的大小可以从 1 MB 到 32 MB 不等,并且必须是 2 的幂。
-XX:G1NewSizePercent=5
-XX:G1MaxNewSizePercent=60
年轻代的总大小,在这两个值之间变化,作为当前使用的 Java 堆的百分比。
-XX:G1HeapWastePercent=5 集合中允许的未回收空间以百分比表示。如果候选集合中的可用空间低于该值,则 G1 停止空间回收阶段。
-XX:G1MixedGCCountTarget=8 许多集合中空间回收阶段的预期长度。
-XX:G1MixedGCLiveThresholdPercent=85 在此空间回收阶段不会收集活动对象占用率高于此百分比的老年代区域。

意味着实际值是根据环境根据人体工程学确定的。


与其他收集器的比较

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

  • Parallel GC 只能作为一个整体来压缩和回收老年代的空间。G1 将这项工作逐步分配到多个更短的集合中。这以吞吐量的潜在代价大大缩短了暂停时间。
  • 与 CMS 类似,G1 同时执行部分老年代空间回收。但是,CMS 无法对老年代堆进行碎片整理,最终会遇到长时间的 Full GC。
  • G1 可能比其他收集器表现出更高的开销,由于其并发性质而影响吞吐量。

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

  • G1 可以在任何回收过程中回收一些完全空的、大面积的老年代。这可以避免许多其他不必要的垃圾收集,轻松释放大量空间。
  • G1 可以选择同时尝试对 Java 堆上的重复字符串进行去重。

从老年代回收空的大对象总是启用的。您可以使用选项禁用此功能-XX:-G1EagerReclaimHumongousObjects。默认情况下禁用字符串重复数据删除。您可以使用选项启用它-XX:+G1EnableStringDeduplication。