一、背景
2021年2月,收到反馈,视频APP某外围接口高峰期响应慢,影响用户体验。
通过监控发现,接口响应慢次要是P99耗时高引起的,狐疑与该服务的GC无关,该服务典型的一个实例GC体现如下图:
能够看出,在察看周期里:
- 均匀每10分钟Young GC次数66次,峰值为470次;
- 均匀每10分钟Full GC次数0.25次,峰值5次;
可见Full GC十分频繁,Young GC在特定的时段也比拟频繁,存在较大的优化空间。因为对GC进展的优化是升高接口的P99时延一个无效的伎俩,所以决定对该外围服务进行JVM调优。
二、优化指标
- 接口P99时延升高30%
- 缩小Young GC和Full GC次数、进展时长、单次进展时长
因为GC的行为与并发无关,例如当并发比拟高时,不论如何调优,Young GC总会很频繁,总会有不该降职的对象降职触发Full GC,因而优化的指标依据负载别离制订:
指标1:高负载(单机1000 QPS以上)
- Young GC次数缩小20%-30% ,Young GC累积耗时不好转;
- Full GC次数缩小50%以上,单次、累积Full GC耗时缩小50%以上,服务公布不触发Full GC。
指标2:中负载(单机500-600)
- Young GC次数缩小20%-30% ,Young GC累积耗时缩小20%;
- Full GC次数不高于4次/天,服务公布不触发Full GC。
指标3:低负载(单机200 QPS以下)
- Young GC次数缩小20%-30% ,Young GC累积耗时缩小20%;
- Full GC次数不高于1次/天,服务公布不触发Full GC。
三、以后存在的问题
以后服务的JVM配置参数如下:
-Xms4096M -Xmx4096M -Xmn1024M-XX:PermSize=512M-XX:MaxPermSize=512M
单纯从参数上剖析,存在以下问题:
未显示指定收集器
JDK 8默认收集器为ParrallelGC,即Young区采纳Parallel Scavenge,老年代采纳Parallel Old进行收集,这套配置的特点是吞吐量优先,个别实用于后台任务型服务器。
比方批量订单解决、科学计算等对吞吐量敏感,对时延不敏感的场景,以后服务是视频与用户交互的门户,对时延十分敏感,因而不适宜应用默认收集器ParrallelGC,应抉择更适合的收集器。
Young区配比不合理
以后服务次要提供API,这类服务的特点是常驻对象会比拟少,绝大多数对象的生命周期都比拟短,通过一次或两次Young GC就会沦亡。
再看下以后JVM配置:
整个堆为4G,Young区总共1G,默认-XX:SurvivorRatio=8,即无效大小为0.9G,老年代常驻对象大小约400M。
这就意味着,当服务负载较高,申请并发较大时,Young区中Eden + S0区域会迅速填满,进而Young GC会比拟频繁。
另外会引起本应被Young GC回收的对象过早降职,减少Full GC的频率,同时单次收集的区域也会增大,因为Old区应用的是ParralellOld,无奈与用户线程并发执行,导致服务长时间进展,可用性降落, P99响应工夫回升。
未设置
-XX:MetaspaceSize和-XX:MaxMetaspaceSize
Perm区在jdk 1.8曾经过期,被Meta区取代,因而-XX:PermSize=512M -XX:MaxPermSize=512M配置会被疏忽,真正管制Meta区GC的参数为-XX:MetaspaceSize:Metaspace初始大小,64位机器默认为21M左右 -XX:MaxMetaspaceSize:Metaspace的最大值,64位机器默认为18446744073709551615Byte,能够了解为无下限 -XX:MaxMetaspaceExpansion:增大触发metaspace GC阈值的最大要求 -XX:MinMetaspaceExpansion:增大触发metaspace GC阈值的最小要求,默认为340784Byte
这样服务在启动和公布的过程中,元数据区域达到21M时会触发一次Full GC (Metadata GC Threshold),随后随着元数据区域的扩张,会夹杂若干次Full GC (Metadata GC Threshold),使服务公布稳定性和效率降落。
此外如果服务应用了大量动静类生成技术的话,也会因为这个机制产生不必要的Full GC (Metadata GC Threshold)。
四、优化计划/验证计划
下面已剖析出以后配置存在的较为显著的有余,上面优化计划次要先针对性解决这些问题,之后再联合成果决定是否持续深刻优化。
以后支流/优良的收集器蕴含:
- Parrallel Scavenge + Parrallel Old:吞吐量优先,后台任务型服务适宜;
- ParNew + CMS:经典的低进展收集器,绝大多数商用、延时敏感的服务在应用;
- G1:JDK 9默认收集器,堆内存比拟大(6G-8G以上)的时候体现出比拟高吞吐量和短暂的进展工夫;
- ZGC:JDK 11中推出的一款低提早垃圾回收器,目前处在试验阶段;
联合以后服务的理论状况(堆大小,可维护性),咱们抉择ParNew + CMS计划是比拟适合的。
参数抉择的准则如下:
1)Meta区域的大小肯定要指定,且MetaspaceSize和MaxMetaspaceSize大小应设置统一,具体多大要联合线上实例的状况,通过jstat -gc能够获取该服务线上实例的状况。
# jstat -gc 31247S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT37888.0 37888.0 0.0 32438.5 972800.0 403063.5 3145728.0 2700882.3 167320.0 152285.0 18856.0 16442.4 15189 597.209 65 70.447 667.655
能够看出MU在150M左右,因而-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M是比拟正当的。
2)Young区也不是越大越好。
当堆大小肯定时,Young区越大,Young GC的频率肯定越小,但Old区域就会变小,如果太小,略微降职一些对象就会触发Full GC得失相当。
如果Young区过小,Young GC就会比拟频繁,这样Old区就会比拟大,单次Full GC的进展就会比拟大。因而Young区的大小须要联合服务状况,分几种场景进行比拟,最终取得最合适的配置。
基于以上准则,以下为4种参数组合:
1.ParNew +CMS,Young区扩充1倍
-Xms4096M -Xmx4096M -Xmn2048M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:+UseParNewGC-XX:+UseConcMarkSweepGC-XX:+CMSScavengeBeforeRemark
2.ParNew +CMS,Young区扩充1倍,
去除-XX:+CMSScavengeBeforeRemark(应用【-XX:CMSScavengeBeforeRemark】参数能够做到在从新标记前先执行一次新生代GC)。
因为老年代和年老代之间的对象存在跨代援用,因而老年代进行GC Roots追踪时,同样也会扫描年老代,而如果可能在从新标记前先执行一次新生代GC,那么就能够少扫描一些对象,从新标记阶段的性能也能因而晋升。)
-Xms4096M -Xmx4096M -Xmn2048M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:+UseParNewGC-XX:+UseConcMarkSweepGC
3.ParNew +CMS,Young区扩充0.5倍
-Xms4096M -Xmx4096M -Xmn1536M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:+UseParNewGC-XX:+UseConcMarkSweepGC -XX:+CMSScavengeBeforeRemark
4.ParNew +CMS,Young区不变
-Xms4096M -Xmx4096M -Xmn1024M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:+UseParNewGC-XX:+UseConcMarkSweepGC -XX:+CMSScavengeBeforeRemark
上面,咱们须要在压测环境,对不同负载下4种计划的理论体现进行比拟,剖析,验证。
4.1 压测环境验证/剖析
高负载场景(1100 QPS)GC体现
能够看出,在高负载场景,4种ParNew + CMS的各项指标体现均远好于Parrallel Scavenge + Parrallel Old。其中:
- 计划4(Young区扩充0.5倍)体现最佳,接口P95,P99延时绝对以后计划升高50%,Full GC累积耗时缩小88%, Young GC次数缩小23%,Young GC累积耗时缩小4%,Young区调大后,尽管次数缩小了,但Young区大了,单次Young GC的耗时也大概率会回升,这是合乎预期的。
- Young区扩充1倍的两种计划,即计划2和计划3,体现靠近,接口P95,P99延时绝对以后计划升高40%,Full GC累积耗时缩小81%, Young GC次数缩小43%,Young GC累积耗时缩小17%,略逊于Young区扩充0.5倍,总体体现不错,这两个计划进行合并,不再辨别。
Young区不变的计划在新计划里,体现最差,淘汰。所以在中负载场景,咱们只须要比照计划2和计划4。
中负载场景(600 QPS)GC体现
能够看出,在中负载场景,2种ParNew + CMS(计划2和计划4)的各项指标体现也均远好于Parrallel Scavenge + Parrallel Old。
- Young区扩充1倍的计划体现最佳,接口P95,P99延时绝对以后计划升高32%,Full GC累积耗时缩小93%, Young GC次数缩小42%,Young GC累积耗时缩小44%;
- Young区扩充0.5倍的计划稍逊一些。
综合来看,两个计划体现非常靠近,原则上两种计划都能够,只是Young区扩充0.5倍的计划在业务高峰期的体现更佳,为尽量保障高峰期服务的稳固和性能,目前更偏向于抉择ParNew + CMS,Young区扩充0.5倍计划。
4.2 灰度计划/剖析
为保障笼罩业务的高峰期,抉择周五、周六、周日别离从两个机房随机抉择一台线上实例,线上实例的指标合乎预期后,再进行全量降级。
指标组 xx.xxx.60.6
采纳计划2,即指标计划
-Xms4096M -Xmx4096M -Xmn1536M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:+UseParNewGC-XX:+UseConcMarkSweepGC -XX:+CMSScavengeBeforeRemark
对照组1 xx.xxx.15.215
采纳原始计划
-Xms4096M -Xmx4096M -Xmn1024M-XX:PermSize=512M-XX:MaxPermSize=512M
对照组2 xx.xxx.40.87
采纳计划4,即候选指标计划
-Xms4096M -Xmx4096M -Xmn2048M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:+UseParNewGC-XX:+UseConcMarkSweepGC -XX:+CMSScavengeBeforeRemark
灰度3台机器。
咱们先剖析下Young GC相干指标:
Young GC次数
Young GC累计耗时
Young GC单次耗时
能够看出,与原始计划相比,指标计划的YGC次数缩小50%,累积耗时缩小47%,吞吐量晋升的同时,服务进展的频率大大降低,而代价是单次Young GC的耗时增长3ms,收益是十分高的。
对照计划2即Young区2G的计划整体体现稍逊与指标计划,再剖析Full GC指标。
老年代内存增长状况
Full GC次数
Full GC累计/单次耗时
与原始计划相比,应用指标计划时,老年代增长的速度要迟缓很多,根本在观测周期内Full GC产生的次数从155次缩小至27次,缩小82%,进展工夫均值从399ms缩小至60ms,缩小85%,毛刺也非常少。
对照计划2即Young区2G的计划整体体现逊于指标计划。到这里,能够看出,指标计划从各个维度均远优于原始计划,调优指标也根本达成。
但仔细的同学会发现,指标计划绝对原始计划,"Full GC"(实际上是CMS Background GC)耗时更加安稳,但每个若干次"Full GC"后会有一个耗时很高的毛刺呈现,这象征这个用户申请在这个时刻会进展2-3s,是否进一步优化,给用户一个更加极致的体验呢?
4.3 再次优化
这里首先要剖析这景象背地的逻辑。
对于CMS收集器,采纳的收集算法为Mark-Sweep-[Compact]。
CMS收集器GC的品种:
CMS Background GC
这种GC是CMS最常见的一类,是周期性的,由JVM的常驻线程定时扫描老年代的使用率,当使用率超过阈值时触发,采纳的是Mark-Sweep形式,因为没有Compact这种耗时操作,且能够与用户过程并行,所以CMS的进展会比拟低,GC日志中呈现GC (CMS Initial Mark)字样就代表产生了一次CMS Background GC。
Background GC因为采纳的是Mark-Sweep,会导致老年代内存碎片,这也是CMS最大的弱点。
CMS Foreground GC
这种GC是CMS收集器里真正意义上的Full GC,采纳Serial Old或Parralel Old进行收集,呈现的频率就较低,当往往呈现后就会造成较大的进展。
触发CMS Foreground GC的场景有很多,场景的如下:
- System.gc();
- jmap -histo:live pid;
- 元数据区域空间有余;
- 降职失败,GC日志中的标记为ParNew(promotion failed);
- 并发模式失败,GC日志中的标记为councurrent mode failure字样。
不难推断,指标计划中的毛刺是降职失败或并发模式失败造成的,因为线上没有开启打印gc日志,但也不妨,因为这两种场景的根因是统一的,就是若干次CMS Backgroud GC后造成的老年代内存碎片。
咱们只须要尽可能减少因为老年代碎片触发降职失败、并发模式失败即可。
CMS Background GC由JVM的常驻线程定时扫描老年代的使用率,当使用率超过阈值时触发,该阈值由-XX:CMSInitiatingOccupancyFraction; -XX:+UseCMSInitiatingOccupancyOnly两个参数管制,不设置,默认首次为92%,后续会依据历史状况进行预测,动静调整。
如果咱们固定阈值的大小,将该阈值设置为一个绝对正当的值,既不使GC过于频繁,又能够升高降职失败或并发模式失败的概率,就能够大大缓解毛刺产生的频率。
指标计划的堆散布如下:
- Young区 1.5G
- Old区 2.5G
- Old区常驻对象 约400M
按教训数据,75%,80%是比拟折中的,因而咱们抉择-XX:CMSInitiatingOccupancyFraction=75 -
XX:+UseCMSInitiatingOccupancyOnly进行灰度察看(咱们也对80%的场景做了对照试验,75%优于80%)。
最终目标计划的配置为:
-Xms4096M -Xmx4096M -Xmn1536M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSScavengeBeforeRemark -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
如上配置,灰度 xx.xxx.60.6 一台机器;
从再次优化的后果上看,CMS Foreground GC引起的毛刺根本隐没,合乎预期。
因而,视频服务最终目标计划的配置为;
-Xms4096M -Xmx4096M -Xmn1536M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSScavengeBeforeRemark -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly
五、后果验收
灰度继续7天左右,笼罩工作日与周末,后果合乎预期,因而合乎在线上开启全量的条件,上面对全量后的后果进行评估。
Young GC次数
Young GC累计耗时
单次Young GC耗时
从Young GC指标上看,调整后Young GC次数均匀缩小30%,Young GC累积耗时均匀缩小17%,Young GC单次耗时均匀减少约7ms,Young GC的体现合乎预期。
除了技术手段,咱们也在业务上做了一些优化,调优前实例的Young GC会呈现显著的、不法则的(定时工作不肯定调配到以后实例)毛刺,这里是业务上的一个定时工作,会加载大量数据,调优过程中将该工作进行分片,摊派到多个实例上,进而使Young GC更加平滑。
Full GC单次/累积耗时
从"Full GC"的指标上看,"Full GC"的频率、进展极大缩小,能够说基本上没有真正意义上的Full GC了。
外围接口-A (上游依赖较多) P99响应工夫,缩小19%(从 3457 ms降落至 2817 ms);
外围接口-B (上游依赖中等) P99响应工夫,缩小41%(从 1647ms降落至 973ms);
外围接口-C (上游依赖起码) P99响应工夫,缩小80%(从 628ms降落至 127ms);
综合来看,整个后果是超出预期的。Young GC体现与设定的指标十分吻合,基本上没有真正意义上的Full GC,接口P99的优化成果取决于上游依赖的多少,依赖越少,成果越显著。
六、写在最初
因为GC算法简单,影响GC性能的参数泛滥,并且具体参数的设置又取决于服务的特点,这些因素都很大水平减少了JVM调优的难度。
本文联合视频服务的调优教训,着重介绍调优的思路和落地过程,同时总结出一些通用的调优流程,心愿能给大家提供一些参考。
作者:vivo互联网技术团队Li Guanyun、 Jessica Chen