关于jvm调优:记一次jvm调优及垃圾收集器

4次阅读

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

本文在第一段先简略解说调优的原因和过程,具体波及到的知识点,在前面段中具体介绍。

1. 调优过程

1.1. 问题定位

有一天忽然收到监控告警,大批量产线服务实例在主动重启。于是连忙上平台下载 dump 日志,以及查看其余监控事件,最终定位到问题:

那几分钟内,涌入几十万用户登录平台操作,导致内存吃紧,简直每个实例都触发了几次 Full GC。而因为集中性的 Full GC,STW 工夫过长,服务测活接口长期调不通,k8s 断定服务故障,就重启 pod。

问题定位了,除了优化代码,缩小有效内存大量占用以外,还能够调优一下 Jvm 参数了。

1.2. gc 问题定位

既然是 gc 出的问题,那就通过 jstat -gcutil pid 时长距离 命令,实时看一下 gc 的过程状态。

通过一段时间察看,发现每次 young gc 后,survivor 区域中占用比例很高(近百分之百),甚至某些次 old 区域中有稍微增长。这阐明一个问题:

young gc 后存活的对象太多,survivor 区寄存不下,溢出的对象就间接进入了老年代。这就放慢了老年代内存的占用速度,提前须要 full gc。

gc 的问题也定位到了,接下来分几个步骤优化

1.3. gc 优化

分了几个维度:

  • 最直观体现是 survivor 区有余,因而能够加大一下年老代中 survivor 比例,即缩小-XX:SurvivorRatio(eden 区和单个 survivor 区的比例,默认值:8)的值。
  • 略微加大一下年老代的占比,即缩小-XX:NewRatio(老年代和年老代的比例,默认值:2)的值。
  • 最基本的还是内存不足,所以如果能够,加大xms/xmx
  • 调优垃圾收集器,缩小 full gc 中 stw 的工夫,防止测活接口长时间停机。因为之前是 jdk 8 默认的(Parallel Scavenge + Parallel Old),换成了更适宜高并发的(ParNew + CMS)。

2. jvm 命令及参数

2.1. jstat -gcutil

留神:出于窃密思考,理论的数据不能在文中展现,下列展现的是与上下文无关的服务数据,是比拟失常的 gc 过程数据:

[root@xxxapi data]# jstat -gcutil 1 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
 50.07   0.00  53.80  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  57.11  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  62.04  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  64.26  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  66.61  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  69.18  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  71.68  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  74.75  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  77.20  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  80.12  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  83.09  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  87.77  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  89.82  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  92.29  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  94.57  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
 50.07   0.00  99.00  13.25  89.91  86.07   1082   21.299     5    2.055   23.354
  0.00  74.61   3.38  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61   7.88  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  11.46  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  14.93  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  18.23  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  21.29  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  25.22  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  28.74  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  31.64  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  36.85  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  39.30  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  44.76  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  48.55  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  51.25  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  54.17  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  58.48  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  61.99  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  64.52  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  67.25  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  70.92  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  74.60  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  78.43  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  82.41  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  86.26  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  90.79  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  93.74  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  95.90  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
  0.00  74.61  99.28  13.25  89.91  86.07   1083   21.325     5    2.055   23.380
 51.43   0.00   2.69  13.29  89.91  86.07   1084   21.345     5    2.055   23.400
 51.43   0.00   5.53  13.29  89.91  86.07   1084   21.345     5    2.055   23.400
 51.43   0.00   8.21  13.29  89.91  86.07   1084   21.345     5    2.055   23.400
  • S0:幸存 1 区以后应用比例
  • S1:幸存 2 区以后应用比例
  • E:伊甸园区应用比例
  • O:老年代应用比例
  • M:元数据区应用比例
  • CCS:压缩应用比例
  • YGC:年老代垃圾回收次数
  • YGCT:年老代垃圾回收耗费工夫
  • FGC:老年代垃圾回收次数
  • FGCT:老年代垃圾回收耗费工夫
  • GCT:垃圾回收耗费总工夫

2.2. 对象进入老年代路径

1. 对象年龄达到阈值后进入老年代

默认状况下,对象在新生代经验了 15 次 GC 后,便会达到进入老年代的条件,将对象转移进入老年代。当然,年龄的阈值能够通过 JVM 参数进行设置:

-XX:MaxTenuringThreshold=10

2. 大对象间接进入老年代

通过以下 JVM 参数进行设置:(留神此参数仅实用于 Serial 和 ParNew 两款新生代收集器。)

-XX:PretenureSizeThreshold=5242880

起因:

  • 大对象须要间断的内存空间,而新生代为了安放大对象可能须要屡次进行 GC,减少开销;
  • 新生代种伊甸园区和幸存者区常采纳复制算法,须要常常复制对象到不同的区域,而大对象在复制时开销较大。

3. 动静抉择进入老年代

HotSpot 虚拟机并不一定会严格依照设置的年龄阈值,满足以下条件也能间接进入老年代:Survivor 区中,年龄从 1 到 n 的对象大小之和超过 Survivor 区的 50% 时,新生代中年龄大于等于 n 的对象将进入老年代。

留神一个误区:这个对象大小总和是按年龄从小到大累加的,并不是同龄对象。

4. young gc 后溢出的进入老年代

在 young gc 后,失常存活对象放入 survivor 区,但如果放不下,存活对象溢出的局部,就会被放入老年代。

3. 垃圾收集器

3.1. 年老代

3.1.1. Serial

Serial 是一类用于新生代的单线程收集器,采纳复制算法进行垃圾收集。Serial 进行垃圾收集时,不仅只用一条单线程执行垃圾收集工作,它还在收集的同时,所用的用户必须暂停。

  • 劣势:简略高效,因为采纳的是单线程的办法,因而与其余类型的收集器相比,对单个 cpu 来说没有了上下文之间的的切换,效率比拟高。
  • 毛病:会在用户不晓得的状况下进行所有工作线程,用户体验感极差,令人难以承受。
  • 实用场景:Client 模式(桌面利用);单核服务器。

参数配置

  • -XX:+UserSerialGC: 抉择 Serial 作为新生代垃圾收集器

3.1.2. ParNew

ParNew 收集器其实就是 Serial 的一个多线程版本,其在单核 cpu 上的体现并不会比 Serail 收集器更好,在多核机器上,其默认开启的收集线程数与 cpu 数量相等。

当用户线程都执行到平安点时,所有线程暂停执行,采纳复制算法进行垃圾收集工作,实现之后,用户线程持续开始执行。

  • 长处:随着 cpu 的无效利用,对于 GC 时系统资源的无效利用有益处。
  • 毛病:和 Serial 是一样的。
  • 实用场景:ParNew 是许多运行在 Server 模式下的虚拟机中首选的新生代收集器。因为CMS 收集器只能与 serial 或者 parNew 联结应用,在当下多核零碎环境下,首选的是 ParNew 与 CMS 配合。ParNew 收集器也是应用 CMS 收集器后默认的新生代收集器。

参数配置

  • -XX:UseParNewGC: 新生代采纳 ParNew 收集器
  • -XX:ParallelGCThreads: 设置 JVM 垃圾收集的线程数

3.1.3. Parallel Scavenge

Parallel Scavenge 也是一款用于新生代的多线程收集器,也是采纳复制算法。与 ParNew 的不同之处在于:

Parallel Scavenge 收集器的目标是达到一个可管制的吞吐量,而 ParNew 收集器关注点在于尽可能的缩短垃圾收集时用户线程的进展工夫。

所谓吞吐量就是 CPU 用于运行用户代码的工夫与 CPU 总耗费工夫的比值,即吞吐量 = 运行用户代码工夫 /(运行用户代码工夫 + 垃圾收集工夫)。
例如虚拟机一共运行了 100 分钟,其中垃圾收集破费了 1 分钟,那吞吐量就是 99%。比方上面两个场景:

  1. 垃圾收集器每 100 秒收集一次,每次进展 10 秒;
  2. 垃圾收集器每 50 秒收集一次,每次进展工夫 7 秒。

尽管后者每次进展工夫变短了,然而总体吞吐量变低了,CPU 总体利用率变低了

  • 长处:谋求高吞吐量,高效利用 CPU,是吞吐量优先,且能进行准确管制。
  • 实用场景:重视吞吐量高效利用 CPU,须要高效运算,且不须要太多交互。

参数配置

  • -XX:+UseParallelOldGC: 默认应用 ParallelOldGC 时候默认新生代应用的是 ParallelScavenge 收集器
  • -XX:MaxGCPauseMilis: 管制最大垃圾收集进展工夫,参数值是一个大于 0 的毫秒数,收集器尽可能保障回收破费工夫不超过设定值。但将这个值调小,并不一定会使零碎垃圾回收速度更快,GC 进展工夫是以就义吞吐量和新生代空间换来的。
  • -XX:GCTimeRadio: 设置吞吐量大小,参数值是一个 (0,100) 两侧均为开区间的整数。也是垃圾收集工夫占总工夫的比率,相当于是吞吐量的倒数。若把参数设置为 19,则容许的最大 GC 工夫就占总工夫的 5%(1/(1+19))。默认值是 99,即容许最大 1% 的垃圾收集工夫。
  • -XX:+UserAdaptiveSizePolicy: 这是一个开关函数,当关上这个函数,就不须要手动指定新生代的大小,Eden 与 Survivor 区的比例 (-XX:SurvivorRatio,默认是 8:1:1),降职老年代的对象年龄(-XX:PretenureSizeThreshold) 等参数。JVM 会动静调整这些参数,以提供最合适的进展工夫或者最大的吞吐量,这种调节形式称为 GC 自适应的调节策略.

3.2. 老年代

3.2.1. Serial Old

Serial Old 是 Serial 收集器的老年代版本,同样是一个单线程收集器,应用 标记 - 整顿算法

  • 实用场景:Client 模式;单核服务器;与 Parallel Scavenge 收集器搭配;作为 CMS 收集器的后备计划,在并发收集产生 Concurrent Mode Failure 时应用

3.2.2. Parallel Old

Parallel Old 是 Parallel Scavenge 收集器的老年代版本,应用 多线程和标记 - 整顿算法,能够充分利用多核 CPU 的计算能力。

  • 实用场景:重视吞吐量与 CPU 资源敏感的场合,与 Parallel Scavenge 收集器搭配应用,jdk7 和 jdk8 默认应用该收集器作为老年代收集器

参数配置

  • -XX:+UserParallelOldGC: 开启 ParallelScavenge + ParallelOld

3.2.3. CMS

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收进展工夫为指标的收集器。采纳的算法是“标记 - 革除”,运作过程分为四个步骤

  • 初始标记,标记 GC Roots 可能间接关联达到对象
  • 并发标记,进行 GC Roots Tracing 的过程
  • 从新标记,修改并发标记期间因用户程序持续运作而导致标记产生变动的那一部分标记记录
  • 并发革除,用标记革除算法革除对象。

** 其中“初始标记”和“从新标记”这两个步骤依然须要 STW(stop the world)。
耗时最长的“并发标记”与“并发革除”过程收集器线程都能够与用户线程一起工作。
总体上来说 CMS 收集器的内存回收过程是与用户线程一起并发执行的。**

  • 长处:并发收集,低进展
  • 毛病:
  • CMS 收集器对 CPU 资源十分敏感,CMS 默认启动对回收线程数(CPU 数量 +3)/4,当 CPU 数量在 4 个以上时,并发回收时垃圾收集线程不少于 25%,并随着 CPU 数量的减少而降落,但当 CPU 数量有余 4 个时,对用户影响较大。
  • CMS 无奈解决浮动垃圾,可能会呈现“Concurrent Mode Failure”失败而导致一次 FullGC 的产生。这时会地洞后备预案,长期用 SerialOld 来从新进行老年代的垃圾收集。因为 CMS 并发清理阶段用户线程还在运行,随同程序运行天然还会有新的垃圾产生,这部分垃圾呈现在标记过程之后,CMS 无奈在当次解决掉,只能等到下一次 GC,这部分垃圾就是浮动垃圾。同时也因为在垃圾收集阶段用户线程还须要运行,那也就须要预留足够的内存空间给用户线程应用,因而 CMS 收集器不能像其余老年代简直齐全填满再进行收集。能够通过参数 -XX:CMSInitiatingOccupancyFraction 批改 CMS 触发的百分比。
  • 因为 CMS 采纳的是标记革除算法,因而垃圾回收后会产生空间碎片。通过参数能够进行优化。

参数配置

  • -XX:+UseConcMarkSweepGC: 启用 cms
  • -XX:ConcGCThreads: 并发的 GC 线程数
  • -XX:+UseCMSCompactAtFullCollection: FullGC 之后做压缩整顿(缩小碎片)
  • -XX:CMSFullGCsBeforeCompaction: 多少次 FullGC 之后压缩一次,默认是 0,代表每次 FullGC 后都会压缩一次
  • -XX:CMSInitiatingOccupancyFraction: 当老年代应用达到该比例时会触发 FullGC(默认是 92,这是百分比)
  • -XX:+UseCMSInitiatingOccupancyOnly: 只应用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction 设定的值),如果不指定,JVM 仅在第一次应用设定值,后续则会主动调整
  • -XX:+CMSScavengeBeforeRemark: 在 CMS GC 前启动一次 minor gc,升高 CMS GC 标记阶段 (也会对年老代一起做标记,如果在 minor gc 就干掉了很多对垃圾对象,标记阶段就会缩小一些标记工夫) 时的开销,个别 CMS 的 GC 耗时 80% 都在标记阶段
  • -XX:+CMSParallellnitialMarkEnabled: 示意在初始标记的时候多线程执行,缩短 STW
  • -XX:+CMSParallelRemarkEnabled: 在从新标记的时候多线程执行,缩短 STW;

3.4. 总结

能够搭配应用的垃圾收集器包含:

  • Serial + Serial old
  • Serial + CMS
  • ParNew + Serial old
  • ParNew + CMS
  • Parallel Scavenge + Serial 0ld
  • Parallel Scavenge + Parallel 0ld

目前罕用的搭配如下:

  • Parrallel Scavenge + Parrallel OldJDK 8 默认收集器搭配。吞吐量优先,后台任务型服务适宜;
  • ParNew + CMS:经典的低进展收集器,绝大多数商用、延时敏感的服务在应用;
  • G1JDK 9 默认收集器,堆内存比拟大(6G-8G 以上)的时候体现出比拟高吞吐量和短暂的进展工夫;
正文完
 0