G1垃圾回收器调优该从哪里开始?

1,388 阅读8分钟

一、G1垃圾回收周期概览

Young-only 阶段:这个阶段从一些普通的年轻对象开始,将对象提升到老年代。当老年代占用率达到初始堆占用阈值时,年轻代和空间回收阶段之间的过渡开始。此时,G1 调度的是 Concurrent Start 年轻收集而不是普通的年轻收集。

  • **Concurrent Start **:除了执行普通的年轻对象回收之外,还会启动标记过程。并发标记决定了老年代区域中所有当前可访问的(活动的)对象,以便为接下来的空间回收阶段保留。当集合标记还没有完全完成时,可能会出现存活的对象被重新持有了引用,必须最终标记一下。
  • 标记(STW):此暂停完成标记本身,执行全局引用处理和类卸载,回收完全空的区域并清理内部数据结构。在 Remark 和 Cleanup 之间 G1 计算信息,以便稍后能够同时回收选定的老年代区域中的空闲空间,这将在 Cleanup 暂停时完成。
  • 清除(STW):这个暂停决定了空间回收阶段是否真的会随之而来。如果随后是空间回收阶段,则仅年轻阶段以单个 Prepare Mixed 年轻待回收结束。

空间回收阶段:这个阶段由多个混合集合组成,除了年轻代区域外,还清除老年代区域集的活动对象。当 G1 确定撤离更多的老年代区域不会产生足够的可用空间时,空间回收阶段结束。

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

二、G1-Oracle的官方建议

一般建议是使用 G1 及其默认设置,最终给它一个不同的暂停时间目标,并根据需要设置最大 Java 堆大小-Xmx。 G1 默认值的平衡与其他收集器不同。G1 在默认配置中的目标既不是最大吞吐量也不是最低延迟,二者取平衡,而是在高吞吐量下提供相对较小、统一的暂停。然而,G1 增量回收堆中空间的机制和暂停时间控制会在应用程序线程和空间回收效率方面产生一些开销。

如果您更喜欢高吞吐量:降低GC停顿时间,或者增加对空间大小。

  • 降低GC停顿时间:设置更小的值 -XX:MaxGCPauseMillis
  • 提供更大的堆来放松暂停时间目标:-Xmx

tips:如果延迟是主要要求,则修改暂停时间目标。避免使用-Xmn-XX:NewRatio等选项将年轻代大小限制为特定值,因为年轻代大小是 G1 允许其满足暂停时间的主要手段。将年轻代大小设置为单个值会覆盖并实际上禁用暂停时间控制。

三、如何提高 G1 性能

Full GC

系统出现Full GC的标识:Full GC可以通过在日志中找到_Pause Full (Allocation Failure)_字样来检测。Full GC 之前通常是遇到to-space exhausted标记指示的疏散失败的垃圾收集。

  • 增加堆空间的大小(-XX:G1HeapRegionSize)但是会增加标记耗时。

  • 显式增加并发标记线程的数量(-XX:ConcGCThreads)

  • 强制 G1 提前开始标记。G1 根据早期的应用程序行为自动确定初始堆占用百分比 (IHOP) 阈值。如果应用程序行为发生变化,这些预测可能是错误的。有两种选择:

    • 显示设置垃圾的占用率:-XX:G1ReservePercent
    • 通过修改增加自适应 IHOP -XX:-G1UseAdaptiveIHOP。来覆盖掉G1自动计算出的值:-XX:InitiatingHeapOccupancyPercent

巨大的对象碎片

  • 程序代码减少庞大对象分配的数量或增加堆空间

由于需要为它们找到一组连续的区域,因此可能会在所有 Java 堆内存耗尽之前发生 Full GC。在这种情况下,潜在的选择是通过增加选项值 -XX:G1HeapRegionSize来减少巨大对象的数量或增加堆的大小来增加堆区域的大小。

不正常的GC操作系统耗时&实际用时

gc.log中:User=0.19s Sys=0.00s Real=0.01s. 用户时间是花在 VM 代码上的时间,_ system time _是在操作系统中花费的时间,以及 _real time_是暂停期间经过的绝对时间量。如果系统时间相对较长,那么可能是VM环境配置导致的。

高系统时间的常见已知问题有:

  • 动态伸缩堆空间导致不必要的延迟。

    • 通过使用选项-Xms和将最小和最大堆大小设置为相同的值来避免延迟-Xmx,并使用预接触所有内存-XX:+AlwaysPreTouch来将此工作移至 VM 启动阶段。
  • 由于某些后台任务间歇性地占用了写入日志的硬盘的所有 I/O 带宽,因此写入日志输出可能会停止一段时间。考虑为您的日志或其他一些存储使用单独的磁盘,例如内存支持的文件系统以避免这种情况。

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

另一种需要注意的情况是实时时间远大于其他情况的总和,这可能表明 VM 可能是在过载的机器上没有获得足够的 CPU 时间。

引用对象处理时间太长

对象的拷贝的过程中需要对RemberSet引用的变更,默认情况下,G1 尝试Reference Processing使用以下启发式并行化子阶段:对于每个-XX:ReferencesPerThread引用对象启动一个线程,由-XX:ParallelGCThreads限制。

  • 通过设置-XX:ReferencesPerThread为 0 默认情况下使用所有可用线程来禁用此启发式方法,
  • 或者完全禁用并行化-XX:-ParallelRefProcEnabled

Young-Only阶段耗时太长

年轻代回收所花费的时间大致与年轻代的大小成正比,或者说,与Collection Set 中需要复制的活动对象的数量成正比。

  • 减少-XX:G1NewSizePercent,缩小堆空间年轻代的占比。
  • 当集合中存活的对象数量突然发生变化,则可能会出现另一个与年轻代大小有关的问题。这可能会导致垃圾收集暂停时间出现峰值。使用 减少最大年轻代大小可能很有用-XX:G1MaxNewSizePercent。这限制了年轻代的最大大小以及暂停期间需要处理的对象数量。

混合收集耗时太长

  • 增加值:-XX:G1MixedGCCountTarget 指定一个周期内触发Mixed GC最大次数,默认值8
  • 通过不将它们放入候选集合集中来避免收集需要大量时间来收集的区域:XX:G1MixedGCLiveThresholdPercent 低于设定值此region归置到CSet,直接影响到Mixed GC选择回收的区域UseLargePages
  • 提高mixedGC的频率:默认值5%,缩小-XX:G1HeapWastePercent,但是会提高mixedGC的频率。超过了指定值触发多次mixedGC

高频率更新 RS 和扫描 RS 次数

  • 通过缩小堆region空间大小:-XX:G1HeapRegionSize
  • 降低Rset停顿耗时:降低参数值:-XX:G1RSetUpdatingPauseTimePercent

调整吞吐量

G1 的默认策略试图在吞吐量和延迟之间保持平衡;但是,在某些情况下需要更高的吞吐量。除了如前几节所述减少总体停顿时间外,还可以减少停顿的频率。主要思想是通过使用增加最大暂停时间-XX:MaxGCPauseMillis

代大小会自动适应年轻代的大小,这直接决定了暂停的频率。如果这没有导致预期的行为,特别是在空间回收阶段,增加最小年轻代大小-XX:G1NewSizePercent将迫使 G1 这样做。

在某些情况下,-XX:G1MaxNewSizePercent允许的最大年轻代大小可能会通过限制年轻代大小来限制吞吐量。

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

关-注 | 收-藏 | 点-赞 都在下方,你确定不三连一下?