https://zhuanlan.zhihu.com/p/269597178

关注的指标

吞吐量

吞吐量=CPU执行用户程序的时间/CPU执行用户程序的时间+GC垃圾回收时间。

停顿时间

GC收集期间需要停止用户工作线程的时长,停顿越久,用户体验越差。

垃圾回收的频率

一段时间内的GC收集的次数,通常是越低越好。

获取指标

在项目启动的时候增加参数来收集GC日志,然后通过第三方的日志分析工具GcEasy(https://gceasy.io/)分析收集的日志来得到吞吐量,停顿时间等统计数据。

PrintGCDetails 开启记录GC日志的详细信息,包括GC类型,操作执行的时间等 PrintGCDateStamps 记录系统的GC时间 loggc 指定日志的保存路径

确定调优的标准

根据业务类型确定”吞吐量优先”还是”停顿时间优先”。
像批处理系统,这种不需要做用户交互的场景,采用吞吐量优先可以提高运算效率。
API层的交互系统,需要考虑停顿时间,不能影响用户的交互体验。
一般而言,我们的调优的大致方向就是在确定允许的停顿时间之内尽最大的努力提高吞吐量,在不能兼得的情况下,需要选择最优的平衡点。

调优策略

1.GC收集器的选择

CPU单核,Serial 垃圾收集器是你唯一的选择
CPU多核,关注吞吐量的,PS+PO的组合
CPU多核,关注用户停顿时间的,使用CMS
CPU多核,关注用户停顿时间的,JDK1.8以上的,使用G1

UseSerialGC UseParallelOldGC UseConcMarkSweepGC UseG1GC

2.增加内存大小

垃圾收集非常频繁,如果内存太小,就会导致需要频繁的进行垃圾收集才能释放出足够的空间来创建新的对象。

-Xms 设置堆的初始值 -Xmx 设置堆的最大值 -xmn 设置新生代的大小

3.设置符合预期的停顿时间

如果程序出现间歇性的卡顿
如果没有设置确定的停顿时间,垃圾收集器以吞吐量为主,那么收集时间就不稳定。

-XX:MaxGCPauseMillis

4.调整内存区域的大小比率

某一个区域的GC频繁,其他都正常
如果对应内存区域不足,就会导致频繁的GC,在JVM堆内存无法增加的情况下,可以调整对应区域的大小比率

survivor区和Eden区大小比率 -XX:SurvivorRatio=6 新生代和老年代的占比 XX:NewRatio=4

5.调整对象升老年代的年龄

老年代频繁GC,每次回收的对象很多
如果升代年龄小,新生代的对象很快就进入老年代,导致老年代的对象变多,而这些对象其实随后很短时间就会被回收。

进入老年代最小的GC年龄,年轻代对象转换为老年代对象最小年龄值,默认值7 -XX:InitialTenuringThreshol=7

6.调整大对象的标准

老年代GC频繁,每次回收的对象很多,而且对象很大。
如果大量的大对象直接分配到老年代,导致老年代容易被填满出现频繁GC

新生代可容纳的最大对象,大于则直接会分配到老年代,0代表没有限制。 -XX:PretenureSizeThreshold=1000000

7.调整GC触发的时机

CMS,G1经常FullGc,程序卡顿严重。
G1和CMS的GC阶段是并发进行的,在GC过程中,预留的空间不足以容纳新创建的对象,JVM会停止并发收集,暂停业务线程。
可以预留更大的空间,调整GC时机。

//使用多少比例的老年代后开始CMS收集,默认是68%,如果频繁发生SerialOld卡顿,应该调小 -XX:CMSInitiatingOccupancyFraction //G1混合垃圾回收周期中要包括的旧区域设置占用率阈值。默认占用率为 65% XX:G1MixedGCLiveThresholdPercent=65

8.调整JVM本地内存大小

GC的次数,时间和回收的对象都正常,堆内存空间充足,但是报OOM,异常信息可能是OutOfMemoryError:Direct buffer memory
本地内存的回收有堆内存的回收连带触发,自己本身不会主动触发GC

1.调整本地内存大小 XX:MaxDirectMemorySize 2.捕获这个异常,手动System.gc()

9.优化业务代码

1.减少非必要的对象
2.主动或者尽早的释放对象
3.尽量少使用大对象,控制好数组,集合里的元素数量

调优实战场景

1.流量暴增,QPS突然增大,导致系统卡顿

初步判断垃圾收集导致业务卡顿
2.jstat -gc查看GC的次数和耗时
3.增大内存并且设置符合预期的停顿时间,避免收集内存过大,导致长时间卡顿

2.后台导出数据引发的OOM

公司的后台系统偶发性出现OOM,堆内存溢出
1.由于偶发性,简单认为是堆内存不足导致的,单方面加大了堆内存,从4G加到8G
2.但问题依然没有解决,只能从堆内存信息入手,通过开启XX:+heapDumpOnOutOfMemoryError参数,获取堆内存的dump文件。
3.使用VisualVM对堆dump文件进行分析,发现List集合对象很大,而且数量也很多,心想估计是某个业务场景使用了大对象,并且在很短时间内并发请求很高。通过对这个VO对象在代码里排查发现,导出订单信息接口,读取数据库的订单信息后,会将订单信息列表封装成List集合,如果日志SQL发现,有些查询的订单量非常大,直接导致一次导出达到10w行数据,并且请求日志也出现同一个时刻,同一个用户出现了多次请求接口的情况,和前端同事反馈之后,通过埋点数据排查出web端由于导出excel耗时过久,操作的用户以为没有响应,操作多次点击导出按钮,触发了多次导出请求。在每次导出数据量很大,且并发请求较多时,堆内存撑满而出现OOM。
4.最后解决方案通过优化导出查询,限制导出的数量,前后端增加重复点击控制。