Java - G1性能调整

The Stages of a Young Collection

一个G1的年轻回收有串行和并行阶段。暂停是串行的,在stop-the-world期间,一些任务只能在另一些任务完成后再执行。并行阶段采用多个GC工作线程,他们有自己的工作队列,当他们的队列完成以后,可以从别的线程偷工作。
年轻回收暂停的串行阶段可以是多线程的,使用-XX:ParallelGCThreads定义GC工作线程的数量。
看下面的GC日志片段

20.225: [GC pause (G1 Evacuation Pause) (young), 0.0454390 secs]
   [Parallel Time: 37.1 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 20225.1, Avg: 20225.3, Max: 20226.2, Diff: 1.1]
      [Ext Root Scanning (ms): Min: 0.1, Avg: 1.7, Max: 5.4, Diff: 5.3, Sum: 13.4]
      [Update RS (ms): Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.3, Sum: 1.0]
         [Processed Buffers: Min: 0, Avg: 2.1, Max: 4, Diff: 4, Sum: 17]
      [Scan RS (ms): Min: 0.1, Avg: 1.6, Max: 3.5, Diff: 3.5, Sum: 13.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 6.2, Max: 24.1, Diff: 24.1, Sum: 49.9]
      [Object Copy (ms): Min: 8.0, Avg: 24.0, Max: 28.8, Diff: 20.8, Sum: 192.2]
      [Termination (ms): Min: 0.0, Avg: 3.1, Max: 3.5, Diff: 3.5, Sum: 24.6]
         [Termination Attempts: Min: 1, Avg: 104.4, Max: 181, Diff: 180, Sum: 835]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.0, Sum: 0.3]
      [GC Worker Total (ms): Min: 35.9, Avg: 36.8, Max: 37.0, Diff: 1.1, Sum: 294.4]
      [GC Worker End (ms): Min: 20262.1, Avg: 20262.1, Max: 20262.1, Diff: 0.0]
   [Code Root Fixup: 2.2 ms]
   [Code Root Purge: 0.2 ms]
   [Clear CT: 0.4 ms]
   [Other: 5.5 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 3.8 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.1 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 1.2 ms]
   [Eden: 1374.0M(1374.0M)->0.0B(1357.0M) Survivors: 26.0M->43.0M Heap: 1412.9M(2048.0M)->45.9M(2048.0M)]
 [Times: user=0.29 sys=0.00, real=0.05 secs] 

该片段显示了一个G1 GC的年轻回收暂停,第一行有G1 Evacuation Pause和young。时间戳是20.225,总暂停时间是0.0454390秒。

Start of All Parallel Activities

第二行是并行阶段的总时间和GC工作线程数量,下面各行是八个线程的主要工作。
GC Worker Start和GC Worker End是并行阶段的开始和结束时间。GC Worker Start的Min timestamp是第一个工作线程的启动时间,GC Worker End的Max timestamp是最后一个工作线程的完成时间。这里也包含以毫秒为单位的Avg和Diff。需要注意的是

  • Diff离0有多远,0是理想值
  • Min、Max和Avg之间的重大差异。这说明并行线程不能同时开始或者完成工作。这意味着队列处理有问题,需要结合并行工作队列做分析

External Root Regions

外部根区扫描是第一个并行任务。在本阶段,外部(off-heap)根,比如JVM的系统字典、VM数据结构、JNI线程handles、硬件寄存器、全局变量和线程堆栈根,被扫描,在任何当前暂停的回收集(CSet)中找出他们。
另一件事情是看一个工作线程是否陷入了处理单个根的问题。比如系统字典就是单个根。

Remembered Sets and Processed Buffers

G1 GC使用RSets来帮助维护和跟踪区的引用。并发的refinement线程,扫描update log buffers和更新区的RSets。为了补充这些线程的工作,任何被记录的还没有被并发refinement线程处理的剩余buffer,会在并行阶段由工作线程处理。这些buffers就是Processed Buffers。
为了限制更新RSets所花费的时间,G1设置一个目标时间,它是暂停时间目标(-XX:MaxGCPauseMillis)的百分比(默认值是10)。
暂停时间主要用来拷贝活的对象,10%的暂停时间是修改RSets的话费。如果你看完日志,认为花10%的时间修改RSets是不可取的,可以修改-XX:G1RSetUpdatingPauseTime参数。如果updated log buffers不改变,在回收暂停期间减少RSet修改时间,会导致只能处理更少的buffers。这会把log buffer的更新工作推给并发refinement线程,导致并发工作增多,和应用的mutator线程共享资源。更坏的是-如果并发refinement线程跟不上log buffer的更新速度,应用mutators必须帮助完成-这是必须避免的。
在回收当前CSet里的区之前,这些区的RSets必须被扫描,区中一个受欢迎的对象或者受欢迎的区能导致它的RSet变coarsened-从一个sparse PRT(per-region table)变成一个fine-grained PRT,甚至变成coarsened位图,导致扫描这样一个RSet的时间变得更长。在这种情况下,你会看到Scan RS时间变大。
另一个和RSets相关的并行任务是代码根扫描-在当前CSet内寻找代码根的引用。

Evacuation and Reclamation

现在,G1知道了当前回收暂停的CSet,可以做暂停中最昂贵的工作了:从CSet区移除活的对象,使用新的自由空间。理想情况下,拷贝对象所占用的时间最长。活的对象需要被拷贝到目标区中分配的线程本地的GC分配缓冲(GCLABs)。

Termination

当每个工作线程的队列都空了以后,上面的工作完成。一个线程请求结束,检查其他线程的工作队列。如果没工作了,它就结束。Termination标签的时间是每个线程在结束协议中使用的时间。

Parallel Activity Outside of GC

Termination标记是回收暂停期间并行工作的结尾。下一行,GC Worker Other,是在暂停期间,GC之外发生的事,此时GC线程停止,比如说编译器的工作(unusual)。

Summarizing All Parallel Activities

GC Worker Total是GC工作线程的总时间(usual和unusual)。

Start of All Serial Activities

并行阶段结束之后,串行阶段开始了:Code Root Fixup、Code Root Purge和Clear CT。在这些时间,主GC线程使用被移动的对象的新位置更新代码根,清洗代码根表。Clear CT阶段(这是在并行工作线程的帮助下并行执行的)清除卡表标记-当扫描RSets的时候,一旦一个卡被扫描,G1 GC就在全局卡表内标记对应的条目,避免重新扫描此卡。

Other Serial Activities

串行阶段的最后一个部分是Other标签。reference enqueuing可能需要更新RSets。Other的时间应该很小。

Young Generation Tunables

-XX:MaxGCPauseMillis是暂停时间目标,默认值是200ms。
-XX:G1NewSizePercent是初始年轻代相对堆的占比,默认值是5。
-XX:G1MaxNewSizePercent是年轻代最大占比,默认值是60。

下来看个例子,设置-XX:MaxGCPauseMillis为50,并且添加参数-XX:+PrintAdaptiveSizePolicy。

130.618: [GC pause (G1 Evacuation Pause) (young) 130.618: [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 3833, predicted base time: 8.99 ms, remaining time: 41.01 ms, target pause time: 50.00 ms]
 130.618: [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 1364 regions, survivors: 36 regions, predicted young region time: 70.04 ms]
 130.618: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 1364 regions, survivors: 36 regions, old: 0 regions, predicted pause time: 79.03 ms, target pause time: 50.00 ms]
, 0.0254544 secs]

上面的例子,暂停时间目标是50毫秒,预测的暂停时间是79.03 ms,实际的暂停时间是25.45毫秒。所以,可以把更多的年轻区加到CSet。

Concurrent Marking Phase Tunables

对于G1,可以调整-XX:InitiatingHeapOccupancyPercent=n(默认值是堆的45%,包括年老区和humongous区)来决定什么时候开始并发标记周期。并发标记周期开始于初始标记暂停,和年轻回收暂停同时开始。这个暂停是回收周期的开始,下来是其他并发和并行任务-根区扫描、并发标记、活性统计、最终标记和清除。下图显示了并发标记周期所有的暂停:初始标记、重新标记和清除。
Young collection pauses

如果程序的活的对象图很大,并且经常被年轻回收暂停所中断,并发标记任务可以运行很长时间。并发标记周期必须在mixed回收暂停开始前完成,并且立即跟着一个年轻回收,用来计算触发下一次mixed回收的阈值。上图显示了一个初始标记暂停。并发阶段可以有多个年轻回收(图里只有一次)。最终标记(也叫remark)完成标记工作,一个小的cleanup暂停用来清理。图中,cleanup暂停后面是一个年轻代evacuation pause,然后是四个mixed暂停,成功回收了目标CSet区的所有垃圾。如果任何并发标记任务所用的时间太长,mixed回收暂停会被推迟,这会导致evacuation failure-在GC日志里,这是to-space耗尽消息,失败的总时间属性会被显示在暂停的Other部分。例如:
to-space exhausted

如果在日志里看到这样的消息,应该尽量避免它:

  • 要设置标记阈值来适合你的程序的静态数据和短命数据的需要。如果你设置的阈值太高,你就可能看到evacuation failure。如果你设置的阈值太低,可能会过早触发并发周期,导致mixed回收不到空间。通常宁可过早开始标记周期而不是太晚以至于发生错误,这是因为evacuation failure的后果比频繁并发标记的更严重
  • 如果你认为标记阈值是正确的,但是并发周期仍然运行很长时间,并且你的mixed回收以“losing the race”来结束回收并触发了evacuation failures,可以增加你的并发线程数量,-XX:ConcGCThreads的默认值是-XX:ParallelGCThreads的1/4。你可以直接增加并发线程数量或者增加并行GC县城数量

A Refresher on the Mixed Garbage Collection Phase

现在,我们已经调整了年轻回收和并发标记周期,我们可以专注于mixed回收周期的老年代回收了。一个mixed回收CSet由所有的年轻区加上一些老年代的区组成。调整mixed回收,能解决mixed回收的CSet中的年老区数量时大时小的问题,增加足够的back-to-back的mixed回收,可以回收所有合格的年老区,化解单个回收的代价。

97.859: [GC pause (G1 Evacuation Pause) (mixed) 97.859: [G1Ergonomics
    (CSet Construction) start choosing CSet, _pending_cards: 28330, predicted
    base time: 17.45 ms, remaining time: 182.55 ms, target pause time: 200.00
    ms]
97.859: [G1Ergonomics (CSet Construction) add young regions to CSet,
    eden: 37 regions, survivors: 14 regions, predicted young region time:
    16.12 ms]
97.859: [G1Ergonomics (CSet Construction) finish adding old regions to
    CSet, reason: old CSet region num reached max, old: 103 regions, max: 103
    regions]
97.859: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 37
    regions, survivors: 14 regions, old: 103 regions, predicted pause time:
    123.38 ms, target pause time: 200.00 ms]
97.905: [G1Ergonomics (Mixed GCs) continue mixed GCs, reason: candidate
    old regions available, candidate old regions: 160 regions, reclaimable:
    66336328 bytes (6.18 %), threshold: 5.00 %]
    , 0.0467862 secs]

第一行告诉我们evacuation pause的类型,这是一个mixed回收,以及CSet选择、把年轻和年老区加到CSet的预测时间。
第五个时间戳,是Mixed GCs,可以看到G1决定继续mixed回收,因为有合格的年老区,而且可回收字节仍然高于默认的5%阈值。
这个例子凸显了两个可调参数:

  • 能被加到CSet的年老区数量(第三个时间戳)
  • 可回收的百分比阈值(第五个)

可回收的百分比阈值,-XX:G1HeapWastePercent,是应用中你能容忍的垃圾总量。默认值是堆的5%。如果mixed回收变得成倍地昂贵,如下图所示,应该增加该阈值。记住,这样做会使更多的区变得支离(fragmented)和被占领。这意味着年老代会保留更多的短暂的活的对象,这样需要相应地调整标记阈值。
Mixed GC collection cycle showing exponentially expensive

一个mixed回收周期内,每次mixed回收暂停的包含在CSet中的年老区的最小阈值是-XX:G1MixedGCCountTarget,默认值是8。
每个mixed回收暂停的最小年老大小=mixed回收周期标识的合格的年老区总量/G1MixedGCCountTarget
该公式定义了,执行y次back-to-back mixed回收可以回收所有合格的年老区。所以,一个完成的并发标记周期之后的back-to-back mixed回收集构成了一次mixed回收周期。
比如

123.563: [G1Ergonomics (CSet Construction) added expensive regions to
    CSet, reason: old CSet region num not reached min, old: 24 regions,
    expensive: 24 regions, min: 24 regions, remaining time: 0.00 ms]

告诉我们,只有24个区被加到CSet,没有达到阈值。上一个年轻回收终端告诉我们,可回收的合格的年老区有189个,所以开始mixed回收周期:

117.378: [G1Ergonomics (Mixed GCs) start mixed GCs, reason: candidate
    old regions available, candidate old regions: 189 regions, reclaimable:
    134888760 bytes (12.56 %), threshold: 5.00 %]

所以,189除以默认的G1MixedGCCountTarget,Ceiling(189/8)=24,这就得到了最小值是24个区。
就像有能加到CSet的年老区的最小阈值,也有最大值。能加到CSet的年老区的最大阈值是-XX:G1OldCSetRegionThresholdPercent,默认值是堆的10%。我们再看个例子:

97.859: [GC pause (G1 Evacuation Pause) (mixed) 97.859: [G1Ergonomics
    (CSet Construction) start choosing CSet, _pending_cards: 28330, predicted
    base time: 17.45 ms, remaining time: 182.55 ms, target pause time: 200.00
    ms]
97.859: [G1Ergonomics (CSet Construction) add young regions to CSet,
    eden: 37 regions, survivors: 14 regions, predicted young region time:
    16.12 ms]
97.859: [G1Ergonomics (CSet Construction) finish adding old regions to
    CSet, reason: old CSet region num reached max, old: 103 regions, max: 103
    regions]
97.859: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 37
    regions, survivors: 14 regions, old: 103 regions, predicted pause time:
    123.38 ms, target pause time: 200.00 ms]
97.905: [G1Ergonomics (Mixed GCs) continue mixed GCs, reason: candidate
    old regions available, candidate old regions: 160 regions, reclaimable:
    66336328 bytes (6.18 %), threshold: 5.00 %]

第三和第五显示了即使有更多的年老区可以回收,当前CSet的年老区总数也最多是103-堆的大小是1G,G1OldCSetRegionThresholdPercent的默认值是10%。
-XX:G1MixedGCLiveThresholdPercent的默认值是85%,它是CSet内的区内活的对象的最大值。每个区的活的对象百分比是在并发标记阶段计算的。一个年老区太昂贵而无法清空-就是说,它的活的对象的百分比还在阈值之上-就不在CSet的候选区内。这个选项直接控制区的碎片化程度。
增加G1MixedGCLiveThresholdPercent,会导致年老区的回收时间变长,mixed回收暂停也跟着变长。

Avoiding Evacuation Failures

有几个重要的调整参数

  • 堆的大小。要确保所有的静态和短命数据都能放进堆
  • 避免过度指定命令行选项。尽量使用默认值。一般只应该调整初始和最大堆,还有希望的暂停时间目标。如果知道默认标记阈值不好,然后再调整。所以,命令行可能是这样的:
    • -Xms2g -Xmx4g -XX:MaxGCPauseMillis=100,或者
    • -Xms2g -Xmx4g -XX:MaxGCPauseMillis=100 -XX:InitiatingHeapOccupancyPercent=55
  • 如果程序有长时间活的humongous对象,确保标记阈值足够小。也要保证你所认为是“humongous”的对象被G1当作长期存活的对象对待。可以设置-XX:G1HeapRegionSize,以确保你认为的巨大的对象大于等于区的50%
  • 有时候,survivor区没有足够的空间容纳新晋升的对象,也会导致evacuation failure。如果发生了,可以增加-XX:G1ReservePercent,它为保留空间设置了一个上限,以容纳晋升的对象。默认值是堆的10%,最大可以达到堆的50%

猜你喜欢

转载自blog.csdn.net/weixin_43364172/article/details/85064363