JVM Tuning Cases in Two Scenarios (Summary of Basic Templates-G1) - Stepping on the Pit Summary Highlights 21 (updated once a week)

JVM tuning

Before we talk about the follow-up sharing content, we must first clarify why we need to tune the jvm? How to tune it? Is there some methodology? Different and pure theoretical sharing, here I will give you two scenarios, so that you can have a deeper understanding of how to adjust the parameters of jvm in which scenarios to better adapt to our system. (I will not tell you about the stinky and long jvm principle here. You can read the information on your own for these basic knowledge. This article will give you specific and effective improvement methodology!)

The purpose of JVM tuning

I personally think that the purpose of tuning is to focus on three things:

  • lower latency
  • Increase throughput
  • smooth out fluctuations

If you want to achieve a significant performance improvement through jvm tuning, it may not be realistic. With this tuning time, it is better to optimize the business code logic and decouple large objects~.

In our usual business, the scenarios we face are mainly divided into TOB and TOC. The TOB scenario is generally the interactive request access of the internal business, and the problems we face are mostly complex CUD operations within the business, which are often read less and write more. Strong data consistency. The TOC scenario is not the same. It is oriented to customers, and has strong demands in terms of performance and experience. It often pursues high availability, high performance, and weak data consistency.

Case 1: The process of jvm tuning of the B-side system and the solution to the problems encountered

TOB-side tuning strategy selection

When dealing with JVM tuning on the TOB side, we must be clear about the problems we face, because JVM tuning is a process of trade-offs like choosing the evolution direction of the system architecture, and you cannot have both . TOB business often has the attributes of complex business, especially the execution of some tasks and the export and import of large files will generate many large objects. If a reasonable jvm parameter is given, it is very likely that large objects frequently trigger fullgc and yanggc to cause pauses, and may even cause memory OOM.

So, the question is, what trade-offs should we make? Let’s start with a B-side business here.

B-side business system scenarios and problems encountered

我们这个业务呢,属于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区占用比例持续增长是由大对象引起的混合回收;

image.png 注意两点

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

image.png

调优尽量减少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更加平稳:

image.png

Tp999提升明显:

image.png

案例二: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调整优化效果:

优化前:

image.png 优化后: image.png

总结

  • 上面两个模板,是面向两个不同的业务场景进行的配置,我们后续的业务也多是基于这两个模板进行微调,所以可以拿去尝试下效果,欢迎补充更多的优化经验

  • jvm调优的效果,远远比不上代码优化、业务拆分解耦带来的性能提升

  • 牢记jvm调优的三个目的:降低延迟、提高吞吐量、抚平波动 他们三者之间是有取舍的,根据自己的业务来调整取舍。

  • 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

Guess you like

Origin juejin.im/post/7118323410689589278