release note

1.3版:调整章节顺序,略微修改概述和标题

1.2版:增加目录信息

1.1版 :增加问题12,补充gc问题挂进程和优化方法论总结的章节,解释最终优化内容。

本文基于JAVA 版本1.8.0_77,其他java版本参数默认值,执行逻辑细节上可能有所差别。

目录

  • 本文亮点
  • gc调优效果
  • 学习的起点-知道自己不知道what
  • g1 gc执行细节
  • 参数调优方式论
    • 增加日志列印
    • 分析统计信息
    • 分析gc日志
    • gc问题挂进程
      • 1.写入过猛引起的进程挂
      • 2.参数问题导致mixed gc连续出现长时间暂停
    • 优化方法论小结
  • 最终优化内容

本文亮点

讲g1原理和参数语义的文章很多,随便谷歌百度一下无数。但大多数都是对官方介绍的翻译转述。很少有文章介绍参数间彼此的影响,调整参数牵一发动全身的影响有哪些。参数调优的方法论,更是没人提及。

本文目标受众是对g1原理和参数有所了解,想进一步了解参数彼此间联系,想在参数调优方面更进一步的同学。

通过一个线上gc优化的实际案例,带你熟悉gc执行细节,了解我总结的参数调优方法论。

如果对g1不了解,可以先搜索了解一下g1原理和参数后再来看本文,会事半功倍。

gc调优效果

线上某分组的regionserver之前存在不稳定的问题。一两个月内总会随机出现1,2台机器,突然cpu飚高,写延时变高引起堆积,找不到问题,只能将问题regionserver移走换一台伺服器代替。后来发现regionserver直接重启也能解决,那是不是regionserver配置有问题?

经调研和论证,确定是gc参数配置问题,参数优化后彻底解决了该问题。

由于伺服器上还部署了datanode,存在一定的资源抢占,regionserver gc时间,间隔有一些波动,但大体是可衡量的。

之前50g堆,young区最大15g,6秒一次young gc,16线程暂停时间100ms左右。 现在100g堆,控制 20g young区,间隔8秒一次young gc,32线程暂停时间大概90ms。

优化前,大概7分钟一次mixed gc周期,优化后大概半小时。

优化前,mixed gc周期内头几次mixed gc之后,mixed gc快速恶化到400-1500毫秒之间,收尾的几次gc基本要秒级以上,gc时间占比短期内达到80%。优化后,mixed gc绝大多数在400ms以内,百分之几的概率在500-900毫秒范围,gc时间基本不会触发超过10%的告警日志列印。

学习的起点-知道自己不知道what

看过文档,知道参数语义了,就可以进行参数调优了吗?

学习有这么几个阶段,什么都不知道,不知道自己不知道,知道自己不知道,知道自己知道。

如果在不知道自己不知道的阶段,误以为自己什么都知道,贸然调优,事倍功半,优化结果南辕北辙。

请看下面的问题,是否有明确的答案?

1.-XX:G1NewSizePercent=5,-XX:G1MaxNewSizePercent=60 是young区起始比例和最大比例的默认值。那么young区有最小比例吗,最小比例是多少?

2.young区的动态大小除了受-XX:MaxGCPauseMillis=100 单次gc最大暂停时间影响,受-XX:G1MaxNewSizePercent=60上限限制,还被其他因素影响吗?

3.-XX:InitiatingHeapOccupancyPercent=45是启动并发mixed gc的已用内存比例阈值,该比例的分母是当前堆内存。那么分子是old区已用,是young+old区已用,还是堆内存已用?

4.一次mixed gc周期内,mixed gc之间的间隔是怎样的,立刻执行下次,有固定时间间隔,还是受其他条件影响?

5.-XX:G1OldCSetRegionThresholdPercent=10 是单次mixed gc扫old区region数的比例。该比例的分母是old区已用region,young+old区已用region,还是堆总region?该比例设置10就会扫10%的region吗,是否受其他条件影响?

6.-XX:G1MixedGCCountTarget=8 一次mixed gc周期mixed gc的目标次数,该数值实际逻辑是什么,8就是8次mixed gc?

7.-XX:G1MixedGCLiveThresholdPercent=85 (其他版本默认值有可能90)要回收region的存活对象内存占比,存活越低回收效果越好。如果存活比例超过85%了,这次mixed gc,这次mixed迭代周期内就一定不会回收了吗?

8.很多技术文章都翻译了官方文档的这句话,mixed gc从可回收比例高的region开始回收。实际执行上是怎么做的?

9.-XX:G1MixedGCCountTarget=8 是一次mixed gc周期mixed gc的次数, -XX:G1OldCSetRegionThresholdPercent=10 是最大扫old区region数的比例,-XX:MaxGCPauseMillis=100 是期待的单次gc最大暂停时间。-XX:G1MixedGCLiveThresholdPercent=85 (其他版本默认值有可能90)是回收region的存活对象内存占比。这几个参数如何决定单次mixed gc清理多少region,一次mixed周期执行多少次mixed gc?

10.其他条件一样,-XX:MaxGCPauseMillis=100 一定比 -XX:MaxGCPauseMillis=200 的单次gc时间短吗?

11.阅读理解,下面三行mixed gc执行日志,说明存在哪些问题?

6949.921: [G1Ergonomics (CSet Construction) finish adding old regions to CSet, reason: predicted time is too high, predicted time: 6.95 ms, remaining time: 0.00 ms, old: 76 regions, min: 76 regions]

6949.921: [G1Ergonomics (CSet Construction) added expensive regions to CSet, reason: old CSet region num not reached min, old: 76 regions, expensive: 70 regions, min: 76 regions, remaining time: 0.00 ms]6949.921: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 79 regions, survivors: 1 regions, old: 76 regions, predicted pause time: 770.76 ms, target pause time: 100.00 ms]

12.坊间传闻,g1内存越大回收效率越差,越不稳定。即使机器资源够分配100g的堆,也只分配50g。这么做有道理吗?

上面各个参数都标注了网上常见的参数解释,了解参数解释你能回答以上的问题吗?如果有疑惑,那么请看下一节。

g1 gc执行细节

-XX:G1NewSizePercent同时是young区最小比例。

young区上限还受old区大小影响,最大不超过90%(默认保留10%)-当前old区大小。当old区朝90%接近时,young区会持续减少直到下限。

-XX:InitiatingHeapOccupancyPercent比例的分子是old区已用。

mixed gc之间是以触发下限young区gc为间隔的,即下限eden区写满时,mixed gc同时清理young区和old区。

-XX:G1OldCSetRegionThresholdPercent比例的分母是堆总region数,100g堆32MB大小region,则总数3200个region,10%就是320个。该比例决定单次mixed gc扫描region数的上限。如果预期扫region时间高于预期剩余时间,则只会扫少量region计算活跃对象比例。

-XX:G1MixedGCCountTarget=8语义是,用触发mixed gc周期时old区的已用region数,除以8,作为每次mixed gc逻辑上最少回收的region数。

一次mixed gc扫描的region中,活跃对象比例高于-XX:G1MixedGCLiveThresholdPercent的region,叫做expensive region,清理代价高。

如果不昂贵的region数不小于最小回收数,那么回收所有不昂贵region,昂贵region按活跃比例在mixed gc周期内整体排序。如果扫描完所有old区region,垃圾比例依然高于-XX:G1HeapWastePercent比例,那么按活跃对象比逆序清理昂贵region,直到比例降低到阈值。

如果总region数大于最小region数但不昂贵region数不够min,则回收min个region,昂贵region中活跃对象比最低的region填补min的缺。

如果min大于实际扫的region,会回收本次mixed gc所有扫瞄过的region数,即使region的活跃对象比超过阈值。

如果-XX:MaxGCPauseMillis过低,预期扫region时间远大于预期剩余时间,那么实际扫的region会小于min数,即使扫的都是昂贵region,依然会全部回收,造成数秒级gc暂停,实际暂停时间反而比-XX:MaxGCPauseMillis大一些要长。

若干次mixed gc后,如果可回收占堆内存比例小于等于-XX:G1HeapWastePercent,本轮mixed gc周期结束。

综上所述,参数有密切的关联关系,调优需要全局权衡。

最后一个问题的日志,由于-XX:MaxGCPauseMillis过低只扫描了少量region,-XX:G1MixedGCCountTarget过低min region数高,昂贵region依然要被回收,暂停时间远大于预期到达秒级,下次扫的region更少,回收昂贵region难以释放内存,持续恶化。堆50g,young区下限2.5g ,间隔不到1秒一次mixed gc,gc时间占比很快超过80%。再加上偶发的memstore内存接近峰值,加上L1读cache,加上静态对象占用的,总不可释放内存比例很高,而-XX:InitiatingHeapOccupancyPercent比例过低,触发mixed gc周期时几乎拷贝了一遍old区所有region,内存也没释放多少空间,regionserver表现出持续的吞吐能力降低,服务不可用现象。

目前只遗留了一个问题,g1是否可以用大堆。容我卖个关子,读者可以结合上面的执行细节,先自己认真思考一下,再往下看。

参数调优方式论

授人以鱼不如授人以渔,我们先来探讨调优的方法论。

调优方法论是,先整体分析gc运行状况,找到瓶颈点或怀疑有问题的地方。仔细翻阅问题发生时间的gc日志,找到有问题的信息,调优。继续观察。

增加日志列印

调优首先要清楚gc运行状况,上一篇gc探索分享里介绍了如何加列印参数,以及如何通过gceasy可视化统计信息。如果没阅读请先看上一篇相关内容。需要额外注意的是,gceasy对g1支持的有点小bug,gc暂停时间图把mixed gc统计到young gc次数里了。如有图里有暂停时间比-XX:MaxGCPauseMillis高一个数量级的暂停时间,都是mixed gc的。

分析统计信息

通过gceasy,我们可以看到gc平均暂停时间,最大暂停时间,应用吞吐占比。gc暂停时间分布,gc前后堆内存变化,gc暂停时间按时间周期的分布图,单次gc垃圾回收内存量的分布图,young区容量分布图,old区容量分布图,young区 promoted分布图,gc内部各阶段时间统计对比,对象创建和升代速度。

通过这些统计信息我们可以了解系统的运行情况,如young 区在多大范围波动,平均young区 gc间隔大概是多久,升代量是多少,mixed gc周期的间隔,每次mixed gc周期回收的old 区内存,等等。掌握这些统计信息,我们就对系统运行情况有了基本了解,可以对系统是否健康做一个初步判断。

不健康主要有两大类,单次暂停时间过长,gc时间占比高。这两个问题又可以引申出单次gc暂停时间过长的次数太多,gc时间占比过高的频率高,mixed gc频率高,每次mixed回收old区内存量太低,等等。

我的经验是,50g或100g的堆内存,如果gc时间占比超过5%,或者gc最大暂停时间超过5秒出现很多次,都是有问题的,需要优化。

如果gc日志跨越好几天,周期性gc时间占比高,但平均gc时间占比可能并不高。如果某一时间段吞吐,延时有问题,可以将这个时间段前后半小时的gc日志截出来,单独上传gceasy。

只看统计信息还不够。本人遇到过日志周期跨越好几天,平均gc时间占比不到2%,最大暂停时间1秒110毫秒,但图表上来看1秒上下的暂停总是连续出现,伴随著周期性业务响应变慢。仔细查看gc日志,gc时间占比在短时间内达到了70%以上,周期性出现。所以需要进一步分析gc日志。

分析gc日志

gc日志内容很多,包括每次gc前堆情况,gc后堆情况,young和mixed gc执行情况。那么我们如何分析呢?

还是要对症下药,找问题出现时前后的日志。

如果暂停时间周期性或偶发出现比预期高一个数量级,可以根据统计信息看到的长时间暂停时间。搜日志,如搜索 real=5找暂停时间5秒到6秒的 ,找到gc上下文,分析为什么慢。

常见以下几种问题

1. max小于等于min

2194074.642: [G1Ergonomics (CSet Construction) finish adding old regions to CSet, reason: old CSet region num reached max, old: 80 regions, max: 80 regions]

2194074.642: [G1Ergonomics (CSet Construction) added expensive regions to CSet, reason: old CSet region num not reached min, old: 80 regions, expensive: 62 regions, min: 105 regions, remaining time: 0.00 ms]2194074.642: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 79 regions, survivors: 1 regions, old: 80 regions, predicted pause time: 374.58 ms, target pause time: 100.00 ms]

没啥说的,改大-XX:G1MixedGCCountTarget,提高max/min的比例吧。我的经验是min最好控制在max的1/4以内,留冗余给昂贵region。

2.predicted time is too high

60998.873: [G1Ergonomics (CSet Construction) finish adding old regions to CSet, reason: predicted time is too high, predicted time: 308.81 ms, remaining time: 0.00 ms, old: 28 regions, min: 28 ree

gions]

60998.873: [G1Ergonomics (CSet Construction) added expensive regions to CSet, reason: old CSet region num not reached min, old: 28 regions, expensive: 27 regions, min: 28 regions, remaining time::

0.00 ms]

由于期望gc执行时间短,预期时间太长,只会扫很少的old区region,甚至可能比min region还少,当遇到连续的高代价region,即使是100%活跃的region也要拷贝,执行时间更长,下次预期时间更长,扫的region数更少,进入恶性循环。单次mixed gc释放不了多少内存,gc时间占比越来越高,有zk超时风险。

本质上,这是设置期望时间太短反而造成暂停时间更长,需要放宽期望gc执行时间,减少young 区最小值,以增大回收old区的可用时间。降低-XX:G1OldCSetRegionThresholdPercent比例以降低预期时间。内存使用上,让不可回收内存比例低一些,避免高存活比例region连续出现的概率,即增大堆内存,增大old区回收阈值,控制memstore,block cache L1的尺寸。要注意的是,memstore增大可以降低写放大,降低磁碟读写IO,增大L1缓存可以提高读缓存命中率。所以这不单单是gc自己的问题,要系统性综合考虑,确定系统的瓶颈究竟是什么,优化哪个问题更重要。

3.reclaimable percentage not over threshold

61007.913: [G1Ergonomics (CSet Construction) finish adding old regions to CSet, reason: reclaimable percentage not over threshold, old: 24 regions, max: 320 regions, reclaimable: 16101191472 bytee

s (15.00 %), threshold: 15.00 %]

61007.913: [G1Ergonomics (CSet Construction) added expensive regions to CSet, reason: old CSet region num not reached min, old: 24 regions, expensive: 24 regions, min: 28 regions, remaining time::

0.00 ms]

到达垃圾保留比例,最后一次mixed gc只会扫很少的region,如果正好都是昂贵的region,则拷贝代价很高。

运气不好的话,这几乎是不可避免的。调大-XX:G1OldCSetRegionThresholdPercent理论上可以让最后一次扫region的平均数量变大,但会造成predicted time is too high更频繁出现。增加堆内存上限和old区回收阈值,提高-XX:G1HeapWastePercent比例,可以更早结束垃圾mixed gc周期,最后一次扫描都是昂贵region的概率也降低了。调大-XX:G1MixedGCCountTarget 让min region更少,可能每次回收量减少一次回收周期时间拉长,需要配合更高的垃圾浪费率和更低的-XX:G1MixedGCLiveThresholdPercent比例,达到快速清理的效果。

gc问题挂进程

gc问题的极端后果是进程挂掉。一般经验认为,内存增加比释放快,内存不足,full gc, oom,进程就挂了。

我遇到过多次gc引起进程挂掉,但目前还没遇到过g1的oom,甚至都没遇到过g1的full gc。这是因为regionserver内存模型young区升代比例很低,另外g1在恶劣条件下gc时间占比很高,即使regionserver压力很大,还没到full gc,就gc时间占比过高引起zk session超时退出了。下面举两个例子。

1.写入过猛引起的进程挂

业务方补一年的数据,hadoop作业写入过猛,还有热点,flush一直排队,compact一直排队,甚至触发hfile上限堵塞写了。写的p99一度飙升到30秒,young gc一次升old区很多,old区内存增长比正常快。结果还没到old区触发mixed gc,由于young gc达到了1秒2,3次,gc时间占比一度超过了95%,开始出现zk session超时,regionserver自动退出。

可以调大region hfile数上限来缓解,但治标不治本。需要控制用户写入,加quota来限制。

2.参数问题导致mixed gc连续出现长时间暂停

regionserver有一定压力,在承受范围内而进程挂了。这是由于参数设置有问题,由于期待暂停时间过低扫的region数不够多,又都是不可回收region,暂停时间越来越长,几次达到8,9秒暂停后,zk session超时退出。

这个按上面的redicted time is too high问题来优化即可。

优化方法论小结

1.-XX:ParallelGCThreads官方推荐是逻辑cpu核数的5/8,注意逻辑cpu核数是物理核的2倍,所以24核可以开到32,young和mixed gc确实变快了。

2.参数调整确保每轮mixed gc的max region数是min region数的4倍以上,降低都是昂贵region的几率。

3.适量增加-XX:MaxGCPauseMillis反而可以降低mixed gc的暂停时间。目的是留给扫描region充足时间,确保每轮mixed gc扫描的region数和期待的max region数相似。

4.如果不想young gc时间也同步变长,可以通过-XX:G1MaxNewSizePercent降低young区最大比例来控制young gc时间。

5.降低最小young 区比例,可以降低mixed gc时回收young 区的时间,从而增加扫描old区region时间,确保扫描更多region。

6.触发mixed gc周期时,old区可回收内存比例越高,越不容易遇到连续昂贵ergion,回收越有效率。所以应该调大堆内存,调高mixed gc触发阈值,控制不可回收内存比例(即memstore和L1 block cache)。

7.当前面条件都满足时,每次mixed gc周期可回收内存比例很高,每轮mixed gc扫描的region数几倍于min region有充足的region挑选不昂贵ergion,可以调高-XX:G1HeapWastePercent比例让本轮mixed gc尽快结束,降低-XX:G1MixedGCLiveThresholdPercent优先回收活跃对象更少的region。

最终优化内容

-Xmx100g -Xms100g 50g -> 100g
-XX:MaxDirectMemorySize= 100g -> 120g
-XX:ConcGCThreads= 4 -> 8
-XX:ParallelGCThreads= 16 -> 32
-XX:G1NewSizePercent= 5 -> 3
-XX:G1MaxNewSizePercent= 60 -> 20
-XX:G1MixedGCCountTarget= 8 -> 64
-XX:G1OldCSetRegionThresholdPercent= 10 -> 4
-XX:InitiatingHeapOccupancyPercent= 65 ->80
-XX:G1HeapWastePercent= 5 -> 20
-XX:G1MixedGCLiveThresholdPercent= 85 -> 80

推荐阅读:

相关文章