关于java:高并发场景下JVM调优实践之路

50次阅读

共计 7633 个字符,预计需要花费 20 分钟才能阅读完成。

一、背景

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 31247
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
37888.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

正文完
 0