G1GC优化方案—官方原版

文章详细介绍了G1垃圾收集器的使用建议、从其他收集器迁移至G1的注意事项、性能提升策略、针对延迟的调整方法以及吞吐量优化。主要关注点包括调整暂停时间目标、堆大小、并发线程数以及处理大对象和记忆集更新。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、对G1的一般建议

一般建议使用G1及其默认设置,最终为其提供不同的暂停时间目标,并根据需要使用-Xmx设置最大Java堆大小。

G1 默认值的平衡方式与其他任何收集器都不同。G1 在默认配置中的目标既不是最大吞吐量也不是最低延迟,而是在高吞吐量下提供相对较小的统一暂停。但是,G1 以增量方式回收堆中的空间和暂停时间控制的机制会在应用程序线程和空间回收效率中产生一些开销。

如果您喜欢高吞吐量,那么可以使用-XX:MaxGCPauseMillis或提供更大的堆来放宽暂停时间目标。如果延迟是主要要求,则修改暂停时间目标。避免使用-Xmn、-XX:NewRatio等选项将年轻一代的大小限制为特定值,因为年轻一代大小是G1满足暂停时间的主要方式。将年轻一代的大小设置为单个值将覆盖并实际上禁用暂停时间控制。

二、从其他收集器移到G1

通常,当从其他收集器(特别是并发标记扫描收集器)移到G1时,首先删除影响垃圾收集的所有选项,然后使用-Xmx和可选-Xms设置暂停时间目标和总堆大小。

许多选项对于其他收集器以某种特定方式响应非常有用,它们要么根本没有效果,要么甚至会降低吞吐量和满足暂停时间目标的可能性。一个例子是设置年轻一代的规模,这完全阻止G1调整年轻一代规模以满足暂停时间目标。

三、提高 G1 性能

G1 旨在提供良好的整体性能,而无需指定其他选项。但是,在某些情况下,默认启发式或默认配置会提供次优结果。本节提供了有关在这些情况下诊断和改进的一些指南。本指南仅介绍 G1 在给定集合应用程序时在选定指标中提高垃圾回收器性能的可能性。根据具体情况,应用程序级优化可能比尝试调整 VM 以使其性能更好更有效,例如,通过完全避免生存期较短的对象出现一些问题情况。

出于诊断目的,G1提供了全面的日志记录。一个好的开始是使用-Xlog:gc*=debug选项,然后根据需要优化输出。日志提供了有关垃圾收集活动暂停期间和暂停之外的详细概述。这包括收集的类型和在暂停的特定阶段花费的时间细分。

1、观察完整的垃圾回收

全堆垃圾收集(FullGC)通常非常耗时。通过在日志中找到单词Pause Full(分配失败),可以检测到旧一代中由于堆占用率过高而导致的GC已满。完整GC之前通常会出现垃圾收集,这些垃圾收集遇到空间耗尽标签指示的疏散失败。

发生完整 GC 的原因是应用程序分配了太多无法足够快地回收的对象。同时进行的标记往往无法及时完成,无法开始空间回收阶段。遇到完整 GC 的可能性可能会因分配许多巨大的对象而复杂化。由于这些对象在 G1 中的分配方式,它们占用的内存可能比预期的要多得多。

目标应该是确保并发标记按时完成。这可以通过降低旧一代的分配率来实现,或者给并发标记更多的时间来完成。

G1 为您提供了几个选项来更好地处理这种情况:

  • 您可以使用gc+heap=info日志记录来确定Java堆上大量对象所占用的区域数。“Humongousregions:X->Y”行中的Y表示由Humongous对象占据的区域数量。如果与旧区域的数量相比,此数量较高,则最好的选择是尝试减少此数量的对象。可以通过使用-XX:G1HeapRegionSize选项增加区域大小来实现此目的。当前选择的堆区域大小将在日志的开头打印。

  • 增加Java堆的大小。这通常会增加完成标记所需的时间。

  • 通过显式设置-XX:ConcGCThreads来增加并发标记线程的数量。

强制G1更早开始标记。G1基于先前的应用程序行为自动确定初始堆占用百分比(IHOP)阈值。如果应用程序行为发生变化,这些预测可能是错误的。有两个选项:通过修改-XX:G1ReservePercent,增加自适应IHOP计算中使用的缓冲区,降低开始空间回收的目标占用率;或者,通过使用-XX:-G1UseAdaptiveIHOP和-XX:InitiatingHeapOccupancyPercent手动设置来禁用IHOP的自适应计算。

除Allocation Failure for a Full GC之外的其他原因通常表示应用程序或某个外部工具导致了完全堆收集。如果原因是System.gc(),并且无法修改应用程序源,则可以使用-XX:+ExplicitGCInvokesConcurrent来减轻Full-gc的影响,或者通过设置-XX:+DisableExplicitGC来让VM完全忽略它们。外部工具仍可能强制执行完整GC;只能通过不请求它们来删除它们。

2、巨大的对象碎片

由于需要为其找到一组连续的区域,在所有Java堆内存耗尽之前,可能会发生完全GC。在这种情况下,可能的选项是通过使用选项-XX:G1HeapRegionSize来增加堆区域大小,以减少大量对象的数量,或者增加堆的大小。在极端情况下,可能没有足够的连续空间供G1分配对象,即使可用内存另有指示。如果FullGC无法回收足够的连续空间,这将导致VM退出。因此,除了减少前面提到的大量对象分配量或增加堆外,别无选择。

四、针对延迟进行调整

本节讨论在出现常见延迟问题(即暂停时间过高)时改进 G1 行为的提示。

1、异常的系统或实时使用

对于每一次垃圾收集暂停,gc+cpu=info日志输出都包含一行,其中包含来自操作系统的信息,并细分了暂停时间的时间。此类输出的一个示例是用户=0.19s Sys=0.00s Real=0.01s。

用户时间是在VM代码中花费的时间,系统时间是在操作系统中花费的,实时是在暂停期间经过的绝对时间。如果系统时间相对较长,那么最常见的原因是环境。

常见已知问题包括:

  • VM从操作系统内存分配或返回内存可能会导致不必要的延迟。通过使用选项-Xms和-Xmx将最小和最大堆大小设置为相同的值,并使用-XX:+AlwaysPreTouch预先触摸所有内存,以将此工作移至VM启动阶段,从而避免延迟。

  • 特别是在Linux中,通过透明巨大页面(THP)功能将小页面合并为巨大页面往往会导致随机进程停止,而不仅仅是在暂停期间。由于VM分配并维护大量内存,因此VM将成为长时间停滞的进程的风险比通常更高。请参阅操作系统的文档,了解如何禁用透明巨大页面功能。

  • 写入日志输出可能会暂停一段时间,因为某些后台任务间歇性地占用了日志所写入硬盘的所有I/O带宽。请考虑为日志或其他存储(例如,内存备份文件系统)使用单独的磁盘来避免这种情况。

需要注意的另一种情况是实时时间比其他时间的总和大得多,这可能表明虚拟机在可能过载的计算机上没有足够的 CPU 时间。

2、引用对象处理花费的时间太长

有关处理参考对象所需时间的信息显示在参考处理阶段。在参考处理阶段,G1根据特定类型的参考对象的要求更新参考对象的参考对象。默认情况下,G1尝试使用以下启发式方法并行化引用处理的子阶段:对于每一个-XX:ReferencesPerThread引用对象,启动一个线程,以-XX:ParallelGCThreads中的值为界。默认情况下,可以通过将-XX:ReferencesPerThread设置为0以使用所有可用线程来禁用此启发式,或者通过-XX:-ParallelRefProcEnabled完全禁用并行化。

3、年轻代的对象收集时间太长

正常的年轻收集,一般来说,任何年轻收集所花费的时间大致与年轻一代的大小成正比,或者更具体地说,与需要复制的收集集中的活动对象的数量成正比。如果“抽空收集集”阶段(特别是“对象复制”子阶段)花费的时间太长,请减少-XX:G1NewSizePercent。这减少了年轻一代的最小规模,允许可能更短的暂停。

如果应用程序性能,特别是集合中幸存的对象数量突然发生变化,年轻一代的规模调整可能会出现另一个问题。这可能会导致垃圾收集暂停时间的峰值。使用-XX:G1MaxNewSizePercent可以减少年轻一代的最大规模。这限制了年轻一代的最大大小,从而限制了暂停期间需要处理的对象的数量。

4、混合收集耗时过长

混合集合用于回收旧一代的空间。混合集合集合包含年轻和旧一代区域。通过启用gc+ergo+cset=跟踪日志输出,您可以获得有关年轻或老一代区域的疏散时间对暂停时间的影响的信息。分别查看年轻一代和老年一代地区的预测年轻地区时间和预测老年地区时间。

如果预测的年轻区域时间太长,请参见“仅年轻阶段内的仅年轻集合”“花费太长时间”了解选项。否则,为了减少旧一代区域对暂停时间的贡献,G1提供了三个选项:

  • 通过增加-XX:G1MixedGCCountTarget,将旧一代区域回收扩展到更多垃圾收集。

  • 通过不使用-XX:G1MixedGCLiveThresholdPercent将区域放入候选集合集,避免收集花费大量时间的区域。在许多情况下,高度占领的地区需要花费大量时间来收集。

  • 尽早停止旧一代空间的回收,这样G1就不会收集那么多高占用区域。在这种情况下,增加-XX:G1HeapWastePercent。

请注意,最后两个选项减少了可以在当前空间回收阶段回收空间的收集集候选区域的数量。这可能意味着G1可能无法在旧一代中回收足够的空间用于持续运行。然而,稍后的空间回收阶段可能能够对它们进行垃圾收集。

5、高更新 RS 和扫描 RS 次数

为了使G1能够疏散单个旧世代区域,G1跟踪跨区域引用的位置,即从一个区域指向另一个区域的引用。指向给定区域的跨区域引用集合称为该区域的记忆集合。移动区域内容时,必须更新记忆的集合。区域记忆集的维护大多是并发的。出于性能目的,当应用程序在两个对象之间安装新的跨区域引用时,G1不会立即更新记住的区域集。为了提高效率,记住的集合更新请求被延迟和批处理。

G1 需要完整的记忆集进行垃圾回收,因此垃圾回收的更新 RS 阶段处理任何未完成的记忆集更新请求。 扫描 RS 阶段在记住的集合中搜索对象参照,移动区域内容,然后将这些对象参照更新到新位置。根据应用的不同,这两个阶段可能需要大量时间。

使用选项-XX:G1HeapRegionSize调整堆区域的大小会影响跨区域引用的数量以及记忆集的大小。处理区域的记忆集可能是垃圾收集工作的重要组成部分,因此这对可实现的最大暂停时间有直接影响。较大的区域往往具有较少的跨区域引用,因此处理这些引用所花费的相对工作量减少,但同时,较大的区域可能意味着每个区域需要疏散更多的活物体,从而增加了其他阶段的时间。

G1尝试安排记忆集更新的并发处理,以便更新RS阶段大约花费允许的最大暂停时间的-XX:G1RSetUpdatingPauseTimePercent百分比。通过减小该值,G1通常同时执行更多的记忆集更新工作。

与分配大型对象的应用程序相结合的虚假高更新RS时间可能是由于优化试图通过批处理来减少并发记忆集更新工作造成的。如果创建此类批处理的应用程序恰好发生在垃圾收集之前,则垃圾收集必须在暂停的更新RS时间部分处理所有这些工作。使用-XX:-ReduceInitialCardMarks禁用此行为并可能避免这些情况。

扫描RS时间也由G1为保持记忆的集合存储大小较低而执行的压缩量确定。存储在内存中的记忆集越紧凑,在垃圾收集期间检索存储值所需的时间就越长。G1自动执行这种压缩,称为记忆集粗化,同时根据该区域记忆集的当前大小更新记忆集。特别是在最高压缩级别,检索实际数据可能非常慢。选项-XX:G1summaryRSetStatsPeriod与gc+remset=跟踪级别日志结合使用可显示是否发生了这种粗化。如果是,那么Before GC Summary部分中的Did<X>粗化行中的X显示一个高值。-XX:G1RSetRegionEntries选项可以显著增加,以减少这些粗化的数量。避免在生产环境中使用这种详细的记忆集日志记录,因为收集这些数据可能会花费大量时间。

五、优化吞吐量

G1的默认策略试图在吞吐量和延迟之间保持平衡;然而,在某些情况下需要更高的吞吐量。除了如前几节所述减少总暂停时间外,还可以减少暂停频率。主要思想是通过使用-XX:MaxGCPauseMillis来增加最大暂停时间。世代大小启发式将自动调整年轻世代的大小,这直接决定暂停的频率。如果这没有导致预期的行为,特别是在空间回收阶段,使用-XX:G1NewSizePercent增加最小年轻一代规模将迫使G1这样做。

在某些情况下,-XX:G1MaxNewSizePercent(允许的最大年轻一代规模)可能会通过限制年轻一代的规模来限制吞吐量。这可以通过查看gc+heap=info日志记录的区域摘要输出来诊断。在这种情况下,Eden区域和Survivor区域的组合百分比接近于区域总数的-XX:G1MaxNewSizePercent%。在这种情况下,考虑增加XX:G1MaxNewSizePercent。

提高吞吐量的另一个选择是尝试减少并发工作量——特别是,并发记忆集更新通常需要大量CPU资源。增加-XX:G1RSetUpdatingPauseTimePercent将工作从并发操作移动到垃圾收集暂停。在最坏的情况下,可以通过设置-XX:-G1UseAdaptiveConcRefinement -XX:G1ConcRefinementGreenZone=2G -XX:G1ConcRefinementThreads=0来禁用并发记忆集更新。这通常会禁用此机制,并将所有记住的集合更新工作移至下一个垃圾收集暂停。

通过使用-XX:+UseLargePages启用大页面的使用也可以提高吞吐量。有关如何设置大页面,请参阅操作系统文档。

您可以通过禁用堆调整工作来最小化堆调整工作;将选项-Xms和-Xmx设置为相同的值。此外,您可以使用-XX:+AlwaysPreTouch将操作系统工作移到具有物理内存的虚拟内存到VM启动时间。为了使暂停时间更加一致,这两种措施都是特别可取的。

六、调整堆大小

与其他收集器一样,G1的目标是调整堆的大小,使垃圾收集所花费的时间低于-XX:GCTimeRatio选项确定的比率。调整此选项以使G1满足您的要求。

七、可调整默认值

本节介绍默认值以及本主题中介绍的命令行选项的一些其他信息。

表8-1 G1 GC可调默认值

Option and Default Value

Description

-XX:+G1UseAdaptiveConcRefinement

-XX:G1ConcRefinementGreenZone=<ergo>

-XX:G1ConcRefinementYellowZone=<ergo>

-XX:G1ConcRefinementRedZone=<ergo>

-XX:G1ConcRefinementThreads=<ergo>

并发记忆集更新(优化)使用这些选项来控制并发优化线程的工作分配。G1为这些选项选择符合人体工程学的值,以便-XX:G1RSetUpdatingPauseTimePercent时间用于垃圾收集暂停以处理任何剩余工作,并根据需要进行自适应调整。小心更换,因为这可能会导致非常长的暂停时间。

-XX:+ReduceInitialCardMarks

这些批处理一起用于初始对象分配的并发记忆集更新(优化)。

-XX:+ParallelRefProcEnabled

-XX:ReferencesPerThread=1000

-XX:ReferencesPerThread决定了并行化的程度:对于每N个引用对象,一个线程将参与引用处理的子阶段,受限于-XX:ParallelGCThreads。值0表示将始终使用值-XX:ParallelGCThreads所指示的最大线程数。

这决定了java.lang.Ref.*实例的处理是否应该由多个线程并行完成。

-XX:G1RSetUpdatingPauseTimePercent=10

这决定了G1在更新RS阶段更新任何剩余的记忆集所花费的总垃圾收集时间的百分比。G1使用此设置控制并发记忆集更新的数量。

-XX:G1SummarizeRSetStatsPeriod=0

这是G1在多个GC中生成记忆集摘要报告的时期。将其设置为零以禁用。生成记住的集合摘要报告是一项成本高昂的操作,因此只有在必要时才应使用它,并且其价值相当高。使用gc+remset=trace打印任何内容。

-XX:GCTimeRatio=12

这是与应用程序相对应的垃圾收集时间目标比率的除数。在增加堆之前,用于确定垃圾收集中可以花费的目标时间比例的实际公式是1/(1+GCTimeRatio)。此默认值将导致目标在垃圾收集中花费大约8%的时间。

-XX:G1PeriodicGCInterval=0

检查G1是否应触发定期垃圾收集的间隔(毫秒)。设置为零以禁用。

-XX:+G1PeriodicGCInvokesConcurrent

如果已设置,则周期性垃圾收集将触发并发标记或继续现有的收集周期,否则将触发完整GC。

-XX:G1PeriodicGCSystemLoadThreshold=0.0

主机getloadavg()调用返回的当前系统负载阈值,用于确定是否应触发定期垃圾收集。当前系统负载高于此值会阻止定期垃圾收集。值为零表示禁用此阈值检查。

欢迎来到Doker,欢迎点赞和评论!或者加微信进入技术群聊!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值