线上FGC调优案例三则

前言

闲鱼服务端应用广泛使用 Java 技术栈,基于JVM提供的托管式堆内存管理,开发者无需过多关心对象创建/回收时的内存分配/释放动作,垃圾回收器(Garbage Collector)会在需要的时候自动清理堆内不再使用的对象,保证有可用空间用于新对象分配。
开发者在享受到自动内存管理的便利时,也不可避免的需要承担垃圾回收器在进行死亡对象清理时的一些开销,例如机器资源利用率上升,应用线程被短暂暂停等。根据JVM使用的垃圾回收算法的不同以及回收的堆区域的不同,GC过程中用户线程的暂停时间、完成一次GC的耗时、也有所不同。
其中,全堆内存清理(Full GC,下称FGC)会扫描整个堆空间,清理掉所有死亡对象,而FGC是Stop the world(下文简称STW)的,也就是意味着会暂停全部的用户线程直到GC结束。一趟FGC的停顿时长视堆空间大小和存活对象数量多少而定,通常从几秒到几十秒不等。在这期间,应用无法对外提供任何服务,表现为接口超时,成功率下降,上游依赖线程池打满等,十分影响用户体验,甚至可能出现访问压力转移到其他健康机器上,使得健康机器对象分配压力增大,最终产生FGC而导致集群雪崩。
对于前台业务应用而言,正常承接业务流量的过程中产生的对象大部分都为朝生夕灭的对象,理论绝大部分对象上应该能通过新生代GC(YGC)被清理掉;而大部分GC算法,都是在老年代占用空间超过一定阈值时,才会触发FGC。因此,一旦出现了FGC,通常意味着这个应用的对象分配或垃圾回收相关的JVM参数存在问题,是业务运行时的重大隐患,需要尽快排查并根治。
本文通过三则前台应用FGC调优案例,分别介绍了由于JVM参数、中间件配置、业务代码层面引起的FGC的现象、分析过程及对应解法。希望能起到抛砖引玉的作用,以及给有同样疑惑的读者朋友们带来一些启发。

商品域核心应用发布及运行时FGC

现象

闲鱼商品域某核心应用,在正常承接业务流量时,集群不时有少量FGC出现,引起接口RT毛刺及成功率略微下跌。在应用发布过程中,集群FGC次数明显上升,接口抖动明显,且大部分FGC出现在未在发布批次的机器上。

此应用承接了对闲鱼商品增删改查操作的绝大部分流量。集群运行时的FGC不仅会影响用户体验,成为以后业务发展过程中的重大隐患,而且发布时的抖动会使得代码万一出问题时没办法快速回退到上一个版本,是安全生产的重大风险,需要尽快解决。

分析

选择一台发生了FGC的机器,查看其JVM信息监控,如下图所示:
5d088ba8ee3ed0a291bfdfaeabb5564b.png

图中不难得出大量有用的信息:

  1. 应用采用 ParNew + CMS组合。

  1. 老年代到达1.7G左右占用时,触发CMS Old Gen GC。【1.7G可能是一个固定的Old Gen回收阈值】

  1. 老年代占用会随着时间的推移缓慢升高,逐渐触顶。【Survivor空间可能不够用】

  2. Old Gen GC 一次回收大约200MB,水位回落到 1.5G,单次回收收益不明显。【老年代存在不少固定占用的大对象】

从监控上看,这是一种非常明显的因为担保分配,使得一些最终会死亡的对象进入到老年代,老年代触到CMS回收阈值产生CMS Old Gen GC的表象。但是在没有充分的证据之前,并不能盲目的下结论,还需要继续小心谨慎地求证上述假设,从而根据深层次的原因制定能根治FGC现象的解决方案。

针对假设1和2,查看应用JVM参数配置:

abf259bc899726d1ddfbdd0e336c9c83.png

可以看到,应用确实是使用CMS作为垃圾回收器,堆大小被配置为4G,Young区2G,Old区2G,CMSInitiatingOccupancyFraction参数被配置为80%,这意味着老年代占用超过80%时触发CMS Old Gen回收,2G*0.8 = 1.6G,差不多就是监控中体现的1.7G左右开始回收,然后老年代占用回落。

另外还可以顺便得到:SurvivorRatio=10,单个Survivor区 2048 * /(10+1+1) = 174762K左右这一结论,能够为接下来的分析提供帮助。

对于假设3,监控系统上以分钟为粒度聚合的折线图就不太能满足需求了,需要通过GC日志,对机器每次GC回收结束后,各个区域的堆空间占用进行分析,看是否存在一次YGC结束后,Survivor区占用较高的情况。一段非常有代表性的GC日志如下图所示:

a9cf05ec1206e7c8e2de4df236969a92.png

注意到ParNew是STW的算法,不存在回收过程中对象分配的可能。而一次ParNew回收前后的堆空间变化可以用下图来表示:

b52983baf2745689e4a83981c6999ddb.png

计算GC日志中各个区域的存活对象占用空间变化:
第一次回收: Young区 40225K存活,全堆1697070K存活,Old区1697070-40225=1656845K
第二次回收: Young区174720K存活,全堆1859440K存活,Old区1859440-174720=1684720K
Old区占用上涨约27875K
而先前通过JVM参数,可以知道单个Survivor区大小约为174762K&#

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值