本文在第一段先简略解说调优的原因和过程,具体波及到的知识点,在前面段中具体介绍。
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% 。比方上面两个场景:
- 垃圾收集器每 100 秒收集一次,每次进展 10 秒;
- 垃圾收集器每 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 Old
:JDK 8 默认收集器搭配。吞吐量优先,后台任务型服务适宜;ParNew
+CMS
:经典的低进展收集器,绝大多数商用、延时敏感的服务在应用;G1
:JDK 9 默认收集器,堆内存比拟大(6G-8G以上)的时候体现出比拟高吞吐量和短暂的进展工夫;