G1 的一般建议
一般建议是使用 G1 及其默认设置,最终给它一个不同的暂停时间目标,并根据需要设置最大 Java 堆大小
-Xmx
。
G1 默认值的平衡与其他收集器不同。G1 在默认配置中的目标既不是最大吞吐量也不是最低延迟,而是在高吞吐量下提供相对较小、统一的暂停。然而,G1 增量回收堆中空间的机制和暂停时间控制会在应用程序线程和空间回收效率方面产生一些开销。
如果您更喜欢高吞吐量,那么通过使用
-XX:MaxGCPauseMillis
或提供更大的堆来降低暂停时间的目标。如果降低延迟是主要要求,则修改暂停时间。避免使用
-Xmn
、
-XX:NewRatio
等选项将年轻代大小限制为特定值,因为年轻代大小是 G1 允许其满足暂停时间的主要手段。将年轻代大小设置为单个值会覆盖并实际上禁用暂停时间控制。
从其他收集器转移到 G1
通常,当从其他收集器(尤其是CMS并发标记清除收集器)转为 G1 时,首先删除所有影响垃圾收集的选项,并仅使用
-Xmx
和可选设置暂停时间目标
-XX:MaxGCPauseMillis
和整体堆大小
-Xms
。
许多选项对于其他收集器以某种特定的方式响应是有用的,它们要么没有任何影响,要么甚至会降低吞吐量和满足暂停时间目标的可能性。 例如,设置年轻代的大小,可以完全阻止G1调整年轻代的大小来满足暂停时间的目标。
提高 G1 性能
G1 旨在提供良好的整体性能,而无需指定其他选项。但是,在某些情况下,它们的默认启发式方法或默认配置会提供次优结果。本节提供了一些有关在这些情况下诊断和改进的指南。本指南仅描述了 G1 在给定一组应用程序时,以所选指标提高垃圾收集器性能的可能性。在个案的基础上,应用程序级优化可能比尝试调整 VM 以提高性能更有效,例如,通过完全避免寿命较短的对象来避免一些问题情况。
出于诊断目的,G1 提供了全面的日志记录。一个好的开始是使用该
-Xlog:gc*=debug
选项,然后在必要时从该选项中细化输出。该日志提供有关垃圾收集活动暂停期间和之外的详细概述。这包括收集的类型和在暂停的特定阶段花费的时间细分。
以下小节探讨了一些常见的性能问题。
观察完整的垃圾收集
一次全堆垃圾回收(Full GC)通常非常耗时。老年代堆占用过高导致的Full GC可以通过在日志中找到
Pause Full (Allocation Failure)
字样来检测。Full GC 之前通常是遇到
to-space exhausted
标记指示的疏散失败的垃圾收集。
发生 Full GC 的原因是应用程序分配了太多无法快速回收的对象。通常并发标记无法及时完成以启动空间回收阶段。由于分配了许多庞大的对象,因此可能会遇到 Full GC。由于这些对象在 G1 中的分配方式,它们可能会占用比预期更多的内存。
目标应该是确保并发标记按时完成。这可以通过降低老年代的分配率来实现,或者给并发标记更多的时间来完成。
G1 为您提供了多种选项来更好地处理这种情况:
- 如果 Java 堆上有大量庞大的对象,则 gc+heap=info 日志记录会在庞大的区域旁边显示该数量。在每次垃圾回收之后,最好的选择是尝试减少对象的数量。您可以通过使用该 -XX:G1HeapRegionSize 选项增加区域大小来实现此目的。当前选择的堆区域大小打印在日志的开头。
- 增加 Java 堆的大小。这通常会增加标记必须完成的时间量。
- 通过 -XX:ConcGCThreads 显式设置增加并发标记线程的数量。
- 强制 G1 提前开始标记。G1 根据早期的应用程序行为自动确定初始堆占用百分比 (IHOP) 阈值。如果应用程序行为发生变化,这些预测可能是错误的。有两个选项:下,用于当通过增加通过修改自适应IHOP计算中使用的缓冲液启动空间回收所述目标占用 -XX:G1ReservePercent ; 或者,通过使用 -XX:-G1UseAdaptiveIHOP 和手动设置来禁用 IHOP 的自适应计算 -XX:InitiatingHeapOccupancyPercent 。
除了完整 GC 的分配失败之外的其他原因通常表明应用程序或某些外部工具导致完整堆收集。如果原因是
System.gc()
,并且无法修改应用程序源,则可以通过使用
-XX:+ExplicitGCInvokesConcurrent
或让 VM 完全忽略它们来减轻 Full GC 的影响
-XX:+DisableExplicitGC
。外部工具可能仍会强制执行 Full GC;只有不请求它们才能删除它们。
巨大的对象碎片
由于需要为它们找到一组连续的区域,因此可能会在所有 Java 堆内存耗尽之前发生 Full GC。在这种情况下,潜在的选择是通过使用选项
-XX:G1HeapRegionSize
来减少巨大对象的数量或增加堆的大小来增加堆区域的大小。在极端情况下,即使可用内存另有指示,也可能没有足够的连续空间可供 G1 分配对象。如果 Full GC 无法回收足够的连续空间,这将导致 VM 退出。因此,除了减少前面提到的庞大对象分配的数量或增加堆外,别无选择。
调整延迟
本节讨论在常见延迟问题(即暂停时间太长)的情况下改进 G1 行为的提示。
异常系统或实时使用
对于每次垃圾收集暂停,
gc+cpu=info
日志输出都包含一行信息,其中包含来自操作系统的信息,并详细说明暂停时间所花费的时间。这种输出的一个例子是
User=0.19s Sys=0.00s Real=0.01s
.
用户时间是在VM代码中花费的时间,
系统时间
是在操作系统中花费的时间,
实时时间
是暂停期间经过的绝对时间量。如果系统时间相对较长,那么最常见的原因是环境。
高系统时间的常见已知问题有:
- VM 从操作系统内存中分配或返还内存可能会导致不必要的延迟。通过使用选项 -Xms 和将最小和最大堆大小设置为相同的值来避免延迟 -Xmx ,并使用预接触所有内存 -XX:+AlwaysPreTouch 来将此工作移至 VM 启动阶段。
- 特别是在 Linux 中,通过 透明大页面 (THP) 功能将小页面合并为大页面往往会导致随机进程停止,而不仅仅是在暂停期间。由于 VM 分配和维护大量内存,因此 VM 将成为长时间停滞的进程的风险高于平常。有关如何禁用透明大页面功能的信息,请参阅您的操作系统文档。
- 由于某些后台任务间歇性地占用了写入日志的硬盘的所有 I/O 带宽,因此写入日志输出可能会停止一段时间。考虑为您的日志或其他一些存储使用单独的磁盘,例如内存支持的文件系统以避免这种情况。
另一种需要注意的情况是实时时间远大于其他情况的总和,这可能表明 VM 在可能过载的机器上没有获得足够的 CPU 时间。
引用对象处理时间太长
有关处理参考对象所用时间的信息显示在
Ref Proc
和
Ref Enq
阶段。在 Ref Proc 阶段,G1 根据其特定类型的要求更新参考对象的所指对象。在 Ref Enq 中,如果发现引用对象已死亡,G1 会将引用对象排入各自的引用队列。如果这些阶段花费的时间太长,请考虑使用选项启用这些阶段的并行化
-XX:+ParallelRefProcEnabled
。
仅限年轻代的收集花费太长时间
Young-only 和一般来说任何年轻的集合所花费的时间大致与年轻代的大小成正比,或者更具体地说,需要复制的集合集中的活动对象的数量。如果
Evacuate Collection Set
阶段花费的时间太长,尤其是
Object Copy
子阶段,请减少
-XX:G1NewSizePercent
。这减少了年轻代的最小大小,允许可能更短的暂停。
如果应用程序性能,特别是集合中存活的对象数量突然发生变化,则可能会出现另一个与年轻代大小有关的问题。这可能会导致垃圾收集暂停时间出现峰值。使用 减少最大年轻代大小可能很有用
-XX:G1MaxNewSizePercent
。这限制了年轻代的最大大小以及暂停期间需要处理的对象数量。
混合集合耗时太长
混合集合用于回收老年代的空间。混合集合的集合包含年轻代和年老代区域。您可以通过启用
gc+ergo+cset=trace
日志输出来获取有关年轻代或年老代区域的疏散对暂停时间有多少贡献的信息。分别查看年轻代区域和年老代区域的
预测年轻区域
时间和
预测年老区域
时间。
- 通过增加 -XX:G1MixedGCCountTarget .
- 通过不使用 - 将它们放入候选集合集中,避免收集需要大量时间来收集的区域 XX:G1MixedGCLiveThresholdPercent 。在许多情况下,高度占用的区域需要花费大量时间来收集。
- 提前停止老年代空间回收,这样 G1 就不会收集那么多的高占用区域。在这种情况下,增加 -XX:G1HeapWastePercent 。
请注意,最后两个选项减少了可以为当前空间回收阶段回收空间的集合集候选区域的数量。这可能意味着 G1 可能无法在老年代回收足够的空间来持续运行。然而,稍后的空间回收阶段可能能够对它们进行垃圾收集。
高更新 RS 和扫描 RS 次数
为了使 G1 能够疏散单个老年代区域,G1 跟踪
跨区域引用的
位置,即从一个区域指向另一个
区域的引用
。指向给定区域的跨区域引用
集
称为该区域的
记忆集
。移动区域的内容时,必须更新记住的集合。区域记忆集的维护大多是并发的。出于性能目的,当应用程序在两个对象之间安装新的跨区域引用时,G1 不会立即更新区域的记忆集。记住的集合更新请求被延迟和批处理以提高效率。
G1 需要完整的记忆集进行垃圾收集,因此垃圾收集的
更新 RS
阶段处理任何未完成的记忆集更新请求。该
扫描RS
在记忆组对象引用阶段的搜索,移动区域中的内容,然后更新到新的位置,这些对象的引用。根据应用的不同,这两个阶段可能需要很长时间。
使用该选项调整堆区域的大小
-XX:G1HeapRegionSize
会影响跨区域引用的数量以及记忆集的大小。处理区域的记忆集合可能是垃圾收集工作的重要组成部分,因此这对可实现的最大暂停时间有直接影响。较大的区域往往具有较少的跨区域引用,因此处理它们所花费的相对工作量会减少,但与此同时,较大的区域可能意味着每个区域要疏散更多的活动对象,从而增加了其他阶段的时间。
G1 尝试调度记忆集更新的并发处理,以便更新 RS 阶段大约占用
-XX:G1RSetUpdatingPauseTimePercent
允许的最大暂停时间的百分比。通过减小这个值,G1 通常会同时执行更多的记忆集更新工作。
与应用程序分配大对象相结合的虚假高更新 RS 时间可能是由尝试通过批处理来减少并发记忆集更新工作的优化引起的。如果创建此类批处理的应用程序恰好在垃圾回收之前发生,则垃圾回收必须在暂停的更新 RS 时间部分处理所有这些工作。使用
-XX:-ReduceInitialCardMarks
禁用此行为并有可能避免这些情况。
扫描 RS 时间也由 G1 执行的压缩量决定,以保持记住的设置存储大小较低。记忆集在内存中存储的越紧凑,在垃圾回收期间检索存储值所需的时间就越多。G1 自动执行这种压缩,称为记忆集粗化,同时根据该区域记忆集的当前大小更新记忆集。特别是在最高压缩级别下,检索实际数据可能会非常缓慢。
-XX:G1SummarizeRSetStatsPeriod
与
gc+remset=trace
级别记录结合的选项显示是否发生这种粗化。如果是这样,则
Before GC Summary
部分
X
中的行
Did coarsenings
中的 将显示高值。这
-XX:G1RSetRegionEntries
可以显着增加选项以减少这些粗化的数量。避免在生产环境中使用这种详细的记住设置日志,因为收集这些数据可能需要大量时间。
调整吞吐量
G1 的默认策略试图在吞吐量和延迟之间保持平衡;但是,在某些情况下需要更高的吞吐量。除了如前几节所述减少总体停顿时间外,还可以减少停顿的频率。主要思想是通过使用增加最大暂停时间
-XX:MaxGCPauseMillis
。代大小启发式会自动适应年轻代的大小,这直接决定了暂停的频率。如果这没有导致预期的行为,特别是在空间回收阶段,增加最小年轻代大小
-XX:G1NewSizePercent
将迫使 G1 这样做。
在某些情况下,
-XX:G1MaxNewSizePercent
允许的最大年轻代大小可能会通过限制年轻代大小来限制吞吐量。这可以通过查看
gc+heap=info
日志记录的区域摘要输出来诊断。在这种情况下,伊甸园区域和幸存者区域的组合百分比接近区域
-XX:G1MaxNewSizePercent
总数的百分比。
-XX:G1MaxNewSizePercent
在这种情况下考虑增加。
增加吞吐量的另一个选择是尝试减少并发工作量,特别是并发记忆集更新通常需要大量 CPU 资源。增加
-XX:G1RSetUpdatingPauseTimePercent
将工作从并发操作转移到垃圾收集暂停。在最坏的情况下,可以通过设置禁用并发记忆集更新
-XX:-G1UseAdaptiveConcRefinement
-XX:G1ConcRefinementGreenZone=2G
-XX:G1ConcRefinementThreads=0
。这主要是禁用此机制并将所有记住的集合更新工作移动到下一个垃圾收集暂停。
通过 using 启用大页面的使用
-XX:+UseLargePages
也可以提高吞吐量。有关如何设置大页面的信息,请参阅操作系统文档。
您可以通过禁用它来最小化堆大小调整工作;将选项
-Xms
和
-Xmx
设置为相同的值。此外,您可以使用
-XX:+AlwaysPreTouch
将操作系统工作移回虚拟内存与物理内存到 VM 启动时间。为了使暂停时间更加一致,这两种措施都可能是特别需要的。
调整堆大小
与其他收集器一样,G1 旨在调整堆大小,以便在垃圾收集上花费的时间低于由
-XX:GCTimeRatio
选项确定的比率。调整此选项以使 G1 满足您的要求。
可调默认值
本节介绍有关本主题中介绍的命令行选项的默认值和一些附加信息。
选项默认值
|
描述
|
-XX:+G1UseAdaptiveConcRefinement
-XX:G1ConcRefinementGreenZone=r go>
-XX:G1ConcRefinementYellowZone=< e rgo>
-XX:G1ConcRefinementRedZone=o >
-XX:G1ConcRefinementThreads=o >
|
并发记忆集更新(细化)使用这些选项来控制并发细化线程的工作分配。G1 为这些选项选择符合人体工程学的值,以便 -XX:G1RSetUpdatingPauseTimePercent 在垃圾收集暂停中花费时间来处理任何剩余的工作,并根据需要自适应地调整它们。请谨慎更改,因为这可能会导致非常长的停顿。
|
-XX:+ReduceInitialCardMarks
|
这将用于初始对象分配的并发记忆集更新(细化)工作批处理在一起。
|
-XX:-ParallelRefProcEnabled
|
这决定了java.lang.Ref.*实例的处理是否应该由多个线程并行完成。
|
-XX:G1RSetUpdatingPauseTimePercent=10
|
这决定了 G1 在更新 RS 阶段应该花费的总垃圾收集时间的百分比来更新任何剩余的记住集。G1 使用此设置控制并发记忆集更新的数量。
|
-XX:G1SummarizeRSetStatsPeriod=0
|
这是 G1 在多个 GC 中生成记住的集合摘要报告的时期。将此设置为零以禁用。生成记住的集合摘要报告是一项代价高昂的操作,因此应该仅在必要时使用它,并且具有相当高的价值。使用 gc+remset=trace 打印任何东西。
|
-XX:GCTimeRatio=12
|
这是应该用于垃圾收集而不是应用程序的目标时间比率的除数。确定在增加堆之前可用于垃圾收集的目标时间比例的实际公式是 1 / (1 + GCTimeRatio) 。这个默认值导致目标有大约 8% 的时间花在垃圾收集上。
|