本节描述垃圾优先(G1)垃圾收集器(GC)。
垃圾优先(G1)垃圾收集器是针对具有大量内存的多处理器机器。 它以高概率满足垃圾收集暂停时间目标,同时在几乎不需要配置的情况下实现高吞吐量。 G1旨在利用当前目标应用程序和环境在延迟和吞吐量之间实现最佳平衡,这些应用程序和环境的功能包括:
1. 堆大小高达数10GB或更大,超过50%的Java堆被实时数据占用。
2. 对象分配和提升的速率可能随着时间的推移而发生显著的变化。
3. 堆中的大量存储残片。
4. 可预测的暂停时间目标不超过几百毫秒,避免长时间的垃圾收集暂停。
G1取代了并发标记扫描(CMS)收集器。它也是默认的收集器。
G1收集器实现了高性能,并尝试以下面描述的几种方式实现暂停时间目标。
垃圾优先(G1)垃圾收集器是默认的收集器,因此通常不需要执行任何其他的操作。你可以通过在命令行上提供-XX:+UseG1GC显式地启用它。
G1是一个分代的、增量的、并行的、多为并发(mostly concurrent)、stop-the-world和回收垃圾收集器,它在每个stop-the-world停顿时时监视暂停时间目标。与其他收集器类似,G1将堆分为(虚拟的)年轻代和年老代。空间回收工作集中在年轻代,这样是最高效的,偶尔也在老年代中进行空间回收。
为了提高吞吐量,有些操作总是在stop-the-world pauses时执行。其他需要花费更多时间停止应用程序的操作,例如全局标记(global marking)等整堆操作,是与应用程序并行执行的。为了进行空间回收时缩短stop-the-world pauses时间,G1逐步并行地进行空间回收。G1通过跟踪有关先前应用程序行为和垃圾收集暂停的信息来建立相关成本模型,从而实现可预测性。它使用此信息来确定暂停中完成的工作的大小。 例如,G1首先在最有效的区域中回收空间(即大多数充满垃圾的区域,因此而得名)。
G1主要通过使用撤离(evacuation)来回收空间:在选定的存储区域中找到的活动对象,复制到新的存储区域中,并在此过程中压缩它们。撤离完成后,活动对象先前占用的空间将被重新用于应用程序的分配。
垃圾优先收集器不是实时收集器。它试图在较长时间内以较高的概率满足设置的暂停时间目标,但并不总是对给定的暂停具有绝对的确定性。
G1将堆划分为一组大小相同的堆区域,每个区域都有一个连续的虚拟内存范围,如图所示。区域是内存分配和内存回收的单位。 在任何给定时间,这些区域中的每一个都可以是空的(浅灰色),或者分配给特定的一代,无论年轻还是老年。当内存请求进入时,内存管理器会分发空闲区域。内存管理器将它们分配给一代,然后将它们作为可以自行分配的可用空间返回给应用程序。
图片说明:
该图由10乘10的网格组成。网格的大部分单元格是灰色的。
十九个单元格是深蓝色的。这些深蓝色的单元格随机分布在网格的上六行。
其中两个深蓝色的单元格包含一个红格子。
两个单元格宽、一个单元格高(出现在第一行)的单元格
和三个单元格宽、一个单元格高(出现在第六行)的单元格为深蓝色,标记为“h”。
八个单元格为浅蓝色,包含一个红色方框。
其中两个单元格被标记为“s”。
这些带有红色方框的浅蓝色单元格随机分布,大多数位于网格的上半部分。
年轻代包含伊甸园(eden)区域(红色)和幸存者(survivor)区域(红色带有“S”)。些区域提供与其他收集器中的相应连续空间相同的功能,不同之处在于G1中这些区域通常以不连续的模式布置在存储器中。Old区(淡蓝色)构成了老年代。对于跨越多个区域的对象,老年代区域可能是巨大的(非常大)(带有“H”的淡蓝色)。
除了直接分配给老一代的庞大对象之外。应用程序总是分配给年轻一代,也就是伊甸园(eden)区域。
在高水平上,G1收集器在两个阶段之间交替。 young-only阶段包含垃圾收集(这些垃圾收集逐渐用旧代中的对象填充当前可用的内存)。空间回收(space-reclamation)阶段是G1除了处理年轻代之外,还以增量的方式回收老年代中的空间。然后,周期从young-only阶段重新开始。
下给出了有关此周期循环的概述,并提供了可能发生的垃圾收集暂停序列的示例:
下面的几点详细描述了G1垃圾收集周期的各个阶段、它们的暂停以及各个阶段之间的转换:
2.1.Young-only阶段:该阶段随着一些年轻代的对象逐步晋升到老年代而开始(通过几个正常的young收集)。当老年代的内存占用率达到一定阈值(即初始堆占用阈值)这两个阶段会发生转换。此时,G1调度的是一个Concurrent Start young收集,而不是一个Normal young收集(jdk12版本)。此时,G1会初始化一次对年轻阶段回收的标记,称为initial mark(jdk9版本)。
(1)Concurrent Start :除了执行正常的年轻代收集之外,还启动了标记过程。 并发标记确定在老年代区域中当前可访问(存活)的所有对象,这些对象将被保存到下一个空间回收阶段。在标记未完成的时候,年轻代回收依然会继续执行。在两次特殊的stw停顿后,标记完成,这两次停顿分别是: Remark和Cleanup.
(2)Remark: Remark会完成所有的标记,并执行全局引用处理和类卸载,回收完全为空的区域并清理内部数据结构。G1在Remark和Cleanup之间计算信息,以便以后能够同时回收选定的老一代区域中的空闲(可用)空间,这将在Cleanup暂停时完成。
(3)Cleanup:该阶段会回收所有的空白区域,并且决定是否接下来要进行空间回收动作。如果接下来需要进行空间回收,将通过一次年轻代的回收动作来完成。
在回收阶段后,回收循环重启,再次来到年轻阶段。另外作为候补机制,当应用内存不足以用来收集所需存活对象信息的时候,G1会启动一次full gc。
2.空间回收(Space-reclamation)阶段:此阶段由若干个次混合的垃圾回收组成,同时分散老年代里的存活对象。随着分散回收的进行,当G1觉得分散更多的老年代也不会产生与之相匹配的空闲着空间时,回收阶段便告一段落。
在回收阶段后,回收循环重启,再次来到年轻阶段。作为备份,如果应用程序在收集活动信息时耗尽内存(或内存不足),G1会像其他收集器一样执行full GC。
G1在stw暂停中执行垃圾回收和空间回收。活动对象(Live objects)通常从源区域复制到堆中的一个或多个目标区域,并调整对这些被移动对象的现有引用。
对于非巨大(non-humongous)区域,对象的目标区域由该对象的源区域确定:
(1)年轻代的对象(eden和survivor区域)根据其年龄被复制到survivor或old区域。
(2)对象从old区域复制到其他old区域。
在巨大(humongous)区域的对象被区别对待。G1只在乎(关注)他们的是否还存活,如果他们没有活着,回收他们占据的空间。在humongous区域的对象永远不会被G1移动。
收集集(Collection Set)是一组用于回收空间的源区域。根据垃圾收集的类型,收集集由不同类型的区域组成:
(1)在Young-Only阶段,收集集仅由Young-Only的区域和具有潜在可回收对象的humongous区域组成。
(2)在空间(Space-Reclamation)回收阶段,收集集由年轻代区域、具有潜在可回收对象的humongous区域和收集集候选区域集中的一些老年代区域组成。
G1在并发周期中准备收集集候选区域。在Remark暂停期间,G1选择占用率较低的区域,这些区域包含大量可用空间。然后,在“Remark”和“Cleanup”暂停之间同时准备这些区域,以便以后收集。Cleanup暂停根据准备的效率对结果进行排序。在随后的混合收集中,更高效的区域(似乎花费更少的时间来进行收集并且包含更多的可用空间)是首选的。
本节描述了垃圾优先(G1)垃圾收集器的一些重要细节。
G1在调整Java堆的大小时遵守标准规则,使用-XX:InitialHeapSize作为最小的Java堆大小,-XX:MaxHeapSize作为最大的Java堆大小,-XX:MinHeapFreeRatio作为可用内存的最小百分比,-XX:MaxHeapFreeRatio用于确定调整大小后可用内存的最大百分比。 G1收集器考虑在Remark 和Full GC暂停时调整Java堆的大小。这个过程可以向操作系统释放内存或从操作系统分配内存。
G1总是在一个正常的young收集的最后为下一个转化(mutator)阶段调整年轻代的大小。通过这种方式,G1可以使用-XX:MaxGCPauseTimeMillis和-XX:PauseTimeIntervalMillis设置的暂停时间目标,这些目标是基于对实际暂停时间的长期观察而设置的。它考虑到了相似大小的年轻代撤离需要多长时间。这包括在收集过程中需要复制多少对象,以及这些对象之间的关联程度等信息。
如果没有其他约束,那么G1在-XX:G1NewSizePercent和-XX:G1MaxNewSizePercent值之间自适应调整年轻代的大小,以满足暂停时间的要求。有关如何修复长暂停的详细信息,请参阅垃圾优先垃圾收集器调优。
或者,-XX:NewSize和-XX:MaxNewSize可以分别设置最小和最大年轻代大小。
注意:
仅指定后两个选项中的一个,
就可以将年轻代的大小精确地
用-XX:NewSize和-XX:MaxNewSize传递的值分别固定。
这将禁用暂停时间控制。