记录一次gc报警背后的思想推演

记录一次gc报警背后的思想推演

问题背景

在日常应用中,我们经常听到4C8G,8C16G等生产机器配置,在出现本次报警之前,我基本上是一个不太经常去看jvm指标的人。问题经历是这样的,在2021年8月14日凌晨,我收到了计费系统频繁YGC的报警,报警内容部分片段如下:

  • 00:05:20至00:05:40,JVM监控YoungGC次数=24次[偏差20%],超过7次YoungGC次数>=20次

收到的内容大致是说平均1秒会发生1次+的YGC,YGC频繁,第一反应就是流量有对应增加,而结果也确实是这样的。拿到报警第一时间开始分析,我的业务流量都是通过worker有频率的去执行的,以此,来保证最终一致性。虽然有接口提供出去,但是很明显接口的调用量并没有报警,所以,第一时间排除了接口调用暴增的可能。而worker一直都是在执行的啊。为什么就会突然出现流量激增呢?

回顾近期上线~,突然意识到,功能虽然没有变更,但是,项目职责却变更了。如下图。这时候突然想起来,原来service app的职责变化了。上线后web和worker的业务逻辑都通过远程调用来支撑,于是乎service app的职责变的更加庞大了。
在这里插入图片描述

于是,首先降低worker app的运行频率或停止热点worker的执行(这需要根据业务评估,由于本worker任务是转历史,可以首先关闭的)。进行风险降低,以防止频繁gc引起老年代空间担保,最终导致频繁full gc,从而导致系统不可用。

问题虽然暂时性的解决了。但是,未从根本上解决。当时脑补的后续补救方案有以下几种

  • 调整worker的执行频率,使得对象的分配更加平滑
  • 调整jvm参数,在业务可以接受stw的时间之内,尽可能的降低gc频率
  • 调整不同应用的机器资源,加大service,减少worker和web.(原因为主逻辑都被service app承担)

jvm优化过程


机器资源和worker频率不在本篇的探讨范围内,而对于上面这一句话。调整jvm参数,在业务可以接受stw的时间之内,尽可能的降低gc频率。这里有两个关键描述

  • 业务可以接受stw的时间
  • 降低gc频率

Stw时间和gc频率,这两个指标在内存区域中是矛盾的:Eden越大,频率越低、stw越长。反之Eden越小、频率越高、stw越小。jvm调优是一个平衡的艺术。我们力求最大吞吐,而不是假设所有的系统都是高并发系统这样的假象。 gc次数 * 单次gc耗时 =stw总耗时,stw总耗时(注意:总耗时要加上full gc的stw耗时)要尽可能小,我们也就达到了最大吞吐。此时的性能就是最优的。(当然,问题不能一概而论。有些高并发系统,stw的耗时是有最低容忍的,即便调优之后整体吞吐变高,但是,单次stw的时间却变长了,这不适合用最大吞吐的思想来解决。)

优化前的jvm参数:


-Xms2048m 
-Xmx2048m 
-Xmn768m 
-Xss512K 
-XX:PermSize=96m 
-XX:MaxPermSize=96m 
-XX:SurvivorRatio=8 
-XX:InitialTenuringThreshold=8 
-XX:MaxTenuringThreshold=8 
-XX:ParallelGCThreads=4 
-XX:TargetSurvivorRatio=70 
-XX:+UseBiasedLocking 
-XX:-UseAdaptiveSizePolicy 
-verbose:gc 
-Xloggc:../gc.log 
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-XX:+PrintAdaptiveSizePolicy 
-XX:+PrintTenuringDistribution 
-XX:+PrintReferenceGC 
-Djava.awt.headless=true 
-Dsun.net.client.defaultConnectTimeout=60000 
-Dsun.net.client.defaultReadTimeout=60000 
-Djmagick.systemclassloader=no -Dnetworkaddress.cache.ttl=300 -Dsun.net.inetaddr.ttl=300
  • 如上,堆设置为2G,其中年轻代设置768m,按照默认比例。Eden区大小为614m,Sur为76m。这样的设定,可以断定主要为了减少stw时间,细节层面也主要包含了以下几点

    • Eden区614m的空间,减少了单次标记的空间
    • sur区76m,减少了单次复制的空间
    • old区1358m,减少了单次full gc的空间。
  • 对应的效果图如下所示

    • YGC

在这里插入图片描述

  • FGC

在这里插入图片描述

  • 并发请求下,峰值可达45次/10s的频率,平均1s 3次ygc,平均耗时11.3ms 。但是在业务高峰期肯定不只11.3ms。
  • 老年代担保机制,触发Full Gc,甚至有可能导致应用不可用。

在4c8g的机器上的2G堆配置,单次stw时间确实短暂,保留在10ms以内,但是这种设定是否是正确的呢 ?在资源利用率方面是否存在严重浪费?

如果基于成本考虑,将4g内存留给系统,剩下的堆内存设置为4g,那么毫无疑问,在每个分代中,对应的堆内存都会加大。单次stw和full gc的stw时间都会由于空间增大而变长。只不过,这个变长的时间是要业务系统可以接受的情况下进行优化。

从系统角度来考虑,10+ms的stw优化无疑是有巨大进步的,但从成本的角度考虑,又存在了严重浪费,从老板的视角来看,是非常不值得的,毕竟为了ms级别的优化,需要每年支付对应的服务器费用,无疑是非常浪费的。

优化后的jvm参数


-Xms4096m
-Xmx4096m
-Xmn1024m
-Xss512K
-XX:SurvivorRatio=9
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:+UseParNewGC
-XX:PretenureSizeThreshold=1m
-XX:+ParallelRefProcEnabled
-XX:+DisableExplicitGC
-XX:InitialTenuringThreshold=6
-XX:MaxTenuringThreshold=6
-XX:ParallelGCThreads=4
-XX:TargetSurvivorRatio=70
-XX:+UseBiasedLocking
-XX:-UseAdaptiveSizePolicy
-XX:+UseConcMarkSweepGC
-XX:ConcGCThreads=4
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=50
-XX:+CMSParallelRemarkEnabled
-XX:CMSFullGCsBeforeCompaction=3
-verbose:gc
-Xloggc:../gc.log
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintAdaptiveSizePolicy
-XX:+PrintTenuringDistribution
-XX:+PrintReferenceGC
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCApplicationConcurrentTime
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
-Djava.awt.headless=true
-Dsun.net.client.defaultConnectTimeout=60000
-Dsun.net.client.defaultReadTimeout=60000
-Djmagick.systemclassloader=no
-Dnetworkaddress.cache.ttl=300
-Dsun.net.inetaddr.ttl=300
  • 增大整个堆空间到4096m,留一半的内存给系统中其他进程使用,尽可能的不要产生swap.
  • 年轻代1024m,Eden和sur比例为9:1:1,其中Eden区为921m,Sur区为102m。相对保守的加大了300m空间。sur区几乎不变
  • -XX:PretenureSizeThreshold=1m,将>1M大的对象提前晋升至老年代,是因为系统中有本地缓存,提前进入old区,减少sur区的拷贝
  • 采用cms垃圾收集器,并在old区50%的时候提前触发full gc
优化后的效果
  • YGC

在这里插入图片描述

  • FGC

在这里插入图片描述

  • 并发请求下,YGC频率最高可达10s 21次,单次stw耗时26ms

  • 无FGC

总结


  • 在业务stw可以接受的情况下,加大堆空间可以使得内存空间有更大的利用率,但是,单次stw时长必须增大,对于不同的系统可以根据实际情况做出对应思考

  • 本次计费worker可以增大堆空间,原因为stw可以稍微长一些,因为是在凌晨来做转历史的任务.所以,该应用可加大对应的新生代空间,以增加资源利用率

  • 如果业务流量可控,那么,可以通过减少worker频率和单次量级来应对机器gc频繁的问题
    在这里插入图片描述

延伸问题


  • 如果stw比较维持在较短的耗时,需求整个堆用到较小的内存空间。那么,4c8g和8c16g的机器在这种应用上是否是必要的 ? 还是用多个机器来实现更好?
  • 在增大资源利用率的情况下,G1垃圾收集器在8C16G上的应用需要实践和调优
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值