在调优JVM的时候,我们的目的是在一定的运行环境下提高吞吐量,降低最大停顿时间。这篇文章以Parallel收集器来进行一次调优实战。
测试环境:青云上海1区A - 性能型 - ubuntu 16.04 - 2核12G
我们要调的是什么?
本文就以我们一个项目的启动速度极慢的Jar包为动手目标,将提升启动速度为目的,那是不太可能的,因为GC的速度本来早就已经优化的很快了,所以提升启动速度的效果不会明显。那我们要调的,要优化的到底是什么?
优化JVM垃圾收集性能从而增大吞吐量或减少停顿时间,让应用在某个业务场景上发挥最大的价值
这是我对JVM调优一个定义,在本文里,将以一个项目的启动过程模拟一个比较消耗资源的Web请求的过程,以在web应用中减少单个请求停顿时间为目的来进行调优。所以评判指标是:Young GC总时间与Full GC总时间,如果总时间无法减少,那么减少最大停顿时间也是优化。
比如,假设单位时间T内发生一次持续25ms的GC,接口平均响应时间为50ms,且请求均匀到达,根据下图所示:
你可能有个疑问,前面不是已经提到,并行(Parallel)收集器适用于计算、后台处理这样的弱交互场景而不是web交互场景。但是我们为什么要用这个收集器来减少停顿时间而不是用CMS或G1收集器呢?因为Parallel有个特点,它支持基于行为的自适应调整,以及其他收集器都不支持的两个参数:-XX:MaxGCPauseMillis=<n>
和-XX:GCTimeRatio=<n>
,从而能精确控制吞吐量与停顿时间,且能自动调整堆的大小,所以虽然它不是偏向减少停顿时间的,但它的表现会更加稳定且可控,也是更加偏向业务场景选择的,所以这个收集器也是有用的。
套路
其实JVM调优的套路非常简单,只需要以下四步即可:
- 明确本次要调优的目标
- 拿到GC日志
- 分析日志
- 调整JVM参数
重复2和3,直到表现令你满意为止。但是实际上第2步是最考验技术实力的一步,你必须要对JVM内存结构、各种垃圾收集器调优的方式、甚至调优经验有一定的积累才能做好,否则将JVM调坏都有可能。本文也将重点介绍如何调整JVM参数以及为什么。
调试时一定要注意本地机器的内存大小是否够用,同时使用java -XX:+PrintFlagsFinal -version | grep MaxHeapSize
查看JVM默认最大堆以及其他最大值来避免影响调试
拿到GC日志
运行Java程序时添加以下参数以输出gc日志
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-verbose:gc
-XX:+PrintTenuringDistribution
-XX:+PrintGCApplicationStoppedTime
-Xloggc:/tmp/gc.log
测试时运行的命令为nohup java *JVM参数* -jar xxx.jar &
期间,使用jps -ml
可以方便的拿到进程PID,从而kill
掉
1 2
| 3144 sun.tools.jps.Jps -ml 2959 cmp-managerServer.jar
|
最后,将/tmp/gc.log
拿到本地,方便查看
第一次分析日志
有一个在线可视化工具方便查看:http://gceasy.io/
将刚生成的log上传上去,拿到结果: