两种场景下的JVM调优案例(总结基础模板-G1)- 踩坑总结集锦 21(一周一更)
Jvm 调优
我们在谈论后续分享内容之前,首先要明确一下,我们为什么要进行jvm调优?如何进行调优?有没有一些方法论? 不同与纯理论的分享,在这里我给大家举两个场景,让大家更深入的了解,在哪些场景下,我们如何调整jvm的参数,更加的适配我们的系统。(我这里不会给大家讲又臭又长的jvm原理,这些基础知识大家自行去翻阅资料,本篇文章会给出具体切实有效的改进方法论!)
JVM调优的目的
我个人认为调优的目的重点有三个:
- 降低延迟
- 提高吞吐量
- 抚平波动
如果您想要通过jvm调优,实现性能的大幅度提升,可能真的是不太现实,有这个调优的时间,不如好好的优化一下业务代码逻辑,解耦大对象~。
我们在平时的业务中,面临的场景主要分为TOB和TOC,TOB的场景一般是内部业务的交互请求访问,面临的问题多是业务内部复杂的CUD操作,往往是读少写多,追求的数据强一致性。而TOC的场景则不然,面向的是Customer,在性能和体验上是有着很强的诉求,往往追求的是高可用、高性能,弱数据一致性。
案例一:B端系统jvm调优的过程以及遇到的问题解决
TOB端调优策略选择
在处理应对TOB端的jvm调优的时候,我们一定要明确我们面临的问题,因为jvm调优如同选择系统架构的演进方向一样,是一个取舍的过程,鱼和熊掌不可兼得。TOB业务往往都具备复杂业务的属性,尤其是一些任务的执行、大文件的导出导入,会产生很多的大对象。如果给出一个合理的jvm参数的话,很可能会出现大对象频繁触发fullgc以及yanggc造成停顿,甚至可能会导致内存的OOM。
于是乎,提出一个疑问,我们应该做出哪些取舍呢?这里就从一个B端业务说起吧。
B端业务系统场景以及遇到的问题
我们这个业务呢,属于B端的一个worker定时任务,用来跑一些定时任务,特点是大多数查询基于mysql数据库的CRUD。因为查询的是数据库,另外包含了一些批量复杂的查询数据结果,所以当时遇到了一个问题就是B端性能的max波动比较大。
B端业务服务器规格以及垃圾回收器类型
- 4核 8G内存 16G磁盘 的机器
- G1垃圾回收器
jvm启动相关配置参数解读
OPTS_MEMORY="-server
-Xms6G ##jvm内存最大值
-Xmx6G ##jvm内存最大可用内存
-XX:MetaspaceSize=256M ##
-XX:MaxMetaspaceSize=256M
-XX:+UseG1GC
-verbose:gc
-Xloggc:/export/Logs/xxxx.local/xxxx-jvm.log
-XX:+PrintGCDetails ##打印详细gc日志
-XX:+PrintGCDateStamps ##打印gc时间戳
-XX:+PrintHeapAtGC ##gc前和gc后打印堆信息
-XX:+PrintGCApplicationConcurrentTime ##应用程序在不停止的情况下工作的时间,即两个连续安全点之间的时间.
-XX:+PrintGCApplicationStoppedTime ##显示应用程序在安全点停止的时间.大多数情况下,安全点是由垃圾收集的世界各个阶段引起的
-XX:+PrintTenuringDistribution ##输出显示在survivor空间里面有效的对象的岁数情况
-XX:+UnlockExperimentalVMOptions ##解锁实验参数
-XX:+UnlockDiagnosticVMOptions ##解锁诊断参数 G1SummarizeRSetStats
-XX:+G1PrintRegionLivenessInfo ##打印在清除阶段每个Region存活对象信息。
-XX:+G1SummarizeRSetStats ##可以确定并发优化线程是否能够及时处理更新日志缓冲区
-XX:+PrintAdaptiveSizePolicy ##可以开启打印启发式算法决策细节
-XX:+PrintReferenceGC ##个参数会打印各种引用的处理时间
-XX:ParallelGCThreads=4 ##gc线程数------------一般配置成和机器核数相等
-XX:G1HeapRegionSize=32M ##每个regions大小-------------2,4,8,16,32 其他参数不生效;
-XX:G1MaxNewSizePercent=90 ##年轻代占比最大空间-------------,Eden区和Survior区的大小之和接近G1MaxNewSizePercent,这个时候就要考虑调大 G1MaxNewSizePercent 。
-XX:MaxGCPauseMillis=150 ##gc最大停顿时间------------当发现eden区分配过少,可以适当提升该配置(年轻代提升可以有效减少yanggc次数)
-XX:InitiatingHeapOccupancyPercent=10
-XX:G1HeapWastePercent=1 ##堆废百分比达到参数值时,停止混合回收-------混合收集时间过长时,可以降低该参数
-XX:G1MixedGCLiveThresholdPercent=85 默认值为85%存活对象超过region的百分之85,不会混合回收,也就是说 有可能造成15的空间浪费
-XX:SoftRefLRUPolicyMSPerMB=1
G1配置参数调整过程中遇到的问题
old区占用比例持续增长的问题原因和解决方式
发生old区占用比例持续增长的原因有很多,可以通过日志分析看是下面那些原因造成的:
1.软引用对象使用较多 rerefrence 导致的怎么办?
解决:可以尝试降低以下参数:XX:SoftRefLRUPolicyMSPerMB=50
2.弱引用对象回收耗时占比高(ThreadLocal)怎么办?
解决: -XX:+ParallelRefProcEnabled #启用弱引用对象的并行回收
3.混合回收的频率低怎么办?
解决:可以尝试调整以下参数:-XX:InitiatingHeapOccupancyPercent=10 # 当老年代占比达到堆大小10%时,下一次gc触发混合回收;
4.空间碎片率较高怎么办?
解决:降低以下参数-XX:G1MixedGCLiveThresholdPercent=85 默认值为85%存活对象超过region的百分之85,不会混合回收,也就是说 有可能造成15的空间浪费
5.大对象区会被jvm监控计入old怎么办?
解决:适当调大region区域:(需要分析)-XX:G1HeapRegionSize=32M ##每个regions大小-------------2,4,8,16, 32 其他参数不生效;
出现以下日志的时候,表示old区占用比例持续增长是由大对象引起的混合回收;
注意两点:
1.Worker定时的频率尽量要小于yanggc的频率,否则定时的产生的垃圾会影响yangGc的效率;
2.在 JDK8u40 之前的版本即便大对象区间是完全空闲的,也只会在井行回收循环的清除暂停阶段才会回收大对象。而JDK8u40 之后,新增了年轻代、Full GC 段针对大对象区间的回收功能,只要大对象区间不再包含任何引用,这些区间就会被回收井且放入空闲区间队列。即年轻代回收、并行回收循环、Full GC ,它们都会参与到大对象区间的回收工作。
压测并发较大的时候发生cpu飙升异常的原因和解决方式
原因:gc太频繁造成的
解决: 提高以下参数,降低混合回收频率:
-XX:InitiatingHeapOccupancyPercent=10 # 当老年代占比达到堆大小10%时,下一次gc触发混合回收;
jvm日志搜索 mix 可以发现G1HeapWastePercent应该调整成2最为合适:
-XX:G1HeapWastePercent=2
调优尽量减少stw(Stop The World)
-
yangGC和fullgc都会存在stw,
-
yangGC时间可控,但是fullgc时间不可控制,风险最大。
-
所以调优总目标就是消灭fullgc,并且在保证每次停顿时间可以接受的前提下,尽量减少yangGC的次数(他们总是成反比);
G1模拟下主要有四种回收方式:
-
Young GC:所有Eden区域满了后触发,并行收集,且完全STW。 并发标记周期:它的第一个阶段初始化标记和YGC一起发生,这个周期的目的就是找到回收价值最大的Region集合(垃圾很多,存活对象很少),为接下来的Mixed GC服务。Mixed
-
Mixed GC:回收所有年轻代的Region和部分老年代的Region,Mixed GC可能连续发生多次。
-
Full GC:非常慢,对OLTP系统来说简直就是灾难,会STW且回收所有类型的Region。
-
YGC:是频率最高的gc
G1垃圾收集过程(yangGc和fullGC的过程类似):
-
初始标记:仅仅是标记GC Roots能直接关联的对象,速度很快。stop the word。
-
并发标记:从GC Roots出发,对堆中对象进行可达性分析,找出存活对象,该阶段耗时较长,但是可与用户线程并发执行。
-
最终标记:主要修正在并发标记阶段因为用户线程继续运行而导致标记记录产生变动的那一部分对象的标记记录。stop the word。
-
筛选阶段:将各个region分区的回收价值和成本进行排序,根据用户所期望的停顿时间制定回收计划。这阶段停顿用户线程。stop the word。
G1调整优化效果:
Tp99更加平稳:
Tp999提升明显:
案例二:C端系统jvm调优的过程以及遇到的问题解决
TOC端调优策略选择
我们的C端服务多是SOA体系下的查询服务,所以这类服务如果按照最理想的方式,对外接口提供的数据都是经过底层数据异构后的数据呈现,特点就是简单、直接、小对象的呈现。针对这种服务的jvm调优,我们多考虑的是压缩gc的停顿时间,来提升接口的性能。
接口的性能=任务线程等待时间(查询中间件)+网络损耗+gc停顿时间+序列化、反序列化时间;
那么我们如何做呢?和B端业务比,我们需要改动哪些参数才能达到降低gc停顿时间的目的呢?
B端业务服务器规格以及垃圾回收器类型
- 8核 16G内存 32G磁盘 的机器
- G1垃圾回收器
jvm启动相关配置参数解读
if [ -z "$OPTS_MEMORY" ] ; then
OPTS_MEMORY="
-server
-Xms10G ##jvm内存最大值
-Xmx10G ##jvm内存最大可用内存
-XX:MetaspaceSize=512M
-XX:MaxMetaspaceSize=512M
-XX:+UseG1GC
-verbose:gc
-Xloggc:/export/Logs/xxxx/xxxx-jvm.log
-XX:+PrintGCDetails ##打印详细gc日志
-XX:+PrintGCDateStamps ##打印gc时间戳
-XX:+PrintHeapAtGC ##gc前和gc后打印堆信息
-XX:+PrintGCApplicationConcurrentTime ##应用程序在不停止的情况下工作的时间,即两个连续安全点之间的时间.
-XX:+PrintGCApplicationStoppedTime ##显示应用程序在安全点停止的时间.大多数情况下,安全点是由垃圾收集的世界各个阶段引起的
-XX:+PrintTenuringDistribution ##输出显示在survivor空间里面有效的对象的岁数情况
-XX:+UnlockExperimentalVMOptions ##解锁实验参数
-XX:+UnlockDiagnosticVMOptions ##解锁诊断参数 G1SummarizeRSetStats
-XX:+G1PrintRegionLivenessInfo ##打印在清除阶段每个Region存活对象信息。
-XX:+G1SummarizeRSetStats ##可以确定并发优化线程是否能够及时处理更新日志缓冲区
-XX:+PrintAdaptiveSizePolicy ##可以开启打印启发式算法决策细节
-XX:+PrintReferenceGC ##个参数会打印各种引用的处理时间
-XX:ParallelGCThreads=8 ##gc线程数------------一般配置成和机器核数相等
-XX:G1HeapRegionSize=16M ##每个regions大小-------------2,4,8,16,32 其他参数不生效;
-XX:G1MaxNewSizePercent=90 ##年轻代占比最大空间-------------,Eden区和Survior区的大小之和接近G1MaxNewSizePercent,这个时候就要考虑调大 G1MaxNewSizePercent
-XX:MaxGCPauseMillis=50 ##gc最大停顿时间------------当发现eden区分配过少,可以适当提升该配置(年轻代提升可以有效减少yanggc次数)
-XX:G1MixedGCLiveThresholdPercent=90 默认值为85%:存活对象超过region的百分之85,不会混合回收,也就是说 有可能造成15的空间浪费
-XX:InitiatingHeapOccupancyPercent=3
-XX:G1HeapWastePercent=1 ##堆废百分比达到参数值时,停止混合回收-------混合收集时间过长时,可以降低该参数
-XX:SoftRefLRUPolicyMSPerMB=1
通过和toB的jvm参数对比得出配置参数的不同点,方便我们来剖析他们之间的区别:
1、ParallelGCThreads(gc线程数)
这个不必多说,gc线程数根据服务器的核数来配置
- toB设置(4核服务器):-XX:ParallelGCThreads=4
- toC设置(8核服务器):-XX:ParallelGCThreads=8 2、G1HeapRegionSize(每个regions大小)
在堆内存耗尽之前,为了找到一系列连续的region也会导致FullGC。这种情况可以通过设置 -XX:G1HeapRegionSize 增大region的大小减少大对象占用的region数量,或者是增加整个堆的大小。极限情况下,虽然看上去还有很多的可用内存,但是G1找不到足够的连续的内存来分配对象。如果FullGC也无法回收足够的可用连续内存就会导致虚拟机退出。这种情况下,只能要么减少大对象的数量,要么就增加堆的大小。
-
TOB:-XX:G1HeapRegionSize=32M B端业务更多的包含大对象,所以为了避免大对象占用region,尽量调大一些
-
TOC:-XX:G1HeapRegionSize=16M C端业务更多的是小对象,region的占用不会很大(如果出现大对象,听劝,解耦你的上帝类吧)
3、MaxGCPauseMillis(gc最大停顿时间)
注意 :并不是说,参数的值设置得更小一点就能使得系统的垃圾收集速度变得更快,因为垃圾收 集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的。
系统把新生代调得小一些,收集256MB新生代肯定比收集512MB快,但这也直接导致垃圾收集发生得更频繁,原来5秒收集一次、每次停顿50毫秒,现在变成2秒收集一次、 每次停顿10毫秒。停顿时间的确在下降,但吞吐量也降下来了。
-
TOB:-XX:MaxGCPauseMillis=150
-
TOC:-XX:MaxGCPauseMillis=50
4、G1MixedGCLiveThresholdPercent(mix gc时,老年代中存活对象的比例不能超过该值) 默认值为85%:存活对象超过region的百分之85,不会混合回收,也就是说 有可能造成15的空间浪费,B端业务大对象会比较多,region的占用不会特别规则,所以适当留出一些空间。C端业务精简后的小对象在region的占用会更细一些,所以提高到来提高空间使用率。
-
TOB:-XX:G1MixedGCLiveThresholdPercent=85
-
TOC:-XX:G1MixedGCLiveThresholdPercent=90
G1调整优化效果:
优化前:
优化后:
总结
转载自:https://juejin.cn/post/7118323410689589278