Parallel Scavenge垃圾回收器线上调优实战

221 阅读4分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

一、前言

之前在使用JDK默认的垃圾回收器时遇到一些问题,于是对该问题进行了具体实验与分析。详情请参考Java默认垃圾回收器Parallel Scavenge导致Full GC增加

通过之前的分析我们知道了默认JDK1.7和JDK1.8的垃圾回收器是Parallel Scavenge。且它会默认启用参数UseAdaptiveSizePolicy, 即开启堆大小自适应策略。这样会导致随着时间的运行PS垃圾回收器的Survivor Space变得越来越小,通知是TT变成1。这会最终导致Full GC的次数增加(相对其他GC)。

针对这个问题,我们只需要通过显示参数**-XX:-UseAdaptiveSizePolicy**来关闭堆大小自适应策略即可。但是应用重启运行一段时间之后,我发现Young  GC优点频繁。于是我编对该应用进行了简单的线上调优。接下来,我们就和大家一起回顾这个调优过程。

二、具体调优过程

1、默认存在的问题

开始调优之前,我们首先来看下默认的Young GC情况。

通过GC监控图我们可以看到,平均10秒就有一到两次Young GC。这个Young GC频率相对来说确实比较频繁。因为该应用的请求量也并不大。而且可以看到Young GC次数波动性比较大。

我们想要调优首先就会考虑到查看当前堆的使用情况。于是我们打印出了当前堆的使用情况(如下图)。

通过堆的使用情况我们可以看到新时代已经快被用完了。但是老年代的剩余空间很多。于是我们可以得知,不断频繁Young GC是因为新时代的空间先对太小,很容易就被占满了。而老年代有很多剩余空间,于是我们自然而然的可以想到,将老年代的空间缩小,新生代的空间扩大。

2、第一次调优情况

通过上面的推论,我们知道这里调优需要调整新生代和老年代的比例,由于之前是使用的默认值2,即NewRatio=2。现在我们将NewRatio改为1,此时新生代和老年代的比例为1:1,即新生代和老年代的大小都为1024M。修改完配置后我们重启应用,并允许一段时间。然后我们观察到Young GC的情况如下:

通过上图我们可以看到Young GC的情况稍微有点好转了。但是似乎还是不是很理想,没有看到比较完美的曲线(完美曲线代表Young GC比较稳定)。于是我们再次打印出堆的使用情况,如下图:

通过上图的堆的使用情况,我们可以看到新时代的空间还是不怎么够用,但是老年代的空间还是剩余比较多。于是我们还可以继续跳转新时代老年代的空间比例。

3、最后一次调优情况

通过上述分析,我们需要再次跳转新时代老年代的比例。但是NewRatio已经被我们调到了最低为1了。于是我们需要放弃这个参数,而改使用-Xmn来直接指定新时代的大小。于是我们制定Xmn=1620M。然后重启应用允许一段时间之后继续观察其Young GC曲线。

通过Young GC曲线,我们可以看到Young GC已经基本保持在20秒1次GC的情况,且比较稳定。于是也就基本达到我们的调优要求。此时我们再次看看堆的使用情况。老年代已经到了69%了,因此如果再调小老年代可能会导致Full GC加剧。于是我们的调优都到此为止吧。

三、调优总结

整体来说这次线上调优其实比较简单,就是通过不断的尝试找到新时代和老年代的一个相对平衡的大小比例。让Young GC比较稳定,且不是那么频繁,同时让Full GC不会太高。所以本次调优重点不在使用了多么复杂的技术,而是在于调优的一个过程。以及遇到类似问题不是简单的通过增加整体堆大小的方式来解决,而是在有限的堆空间内让Young GC和Full GC达到一个平衡。

我想有很多同学都会问两个问题:1、为什么不直接调大整体堆的大小;2、为什么要用PS这个存在Full GC问题的垃圾回收器。

首先第一个问题,上面我也提到过了,调优的目的是对空间的合理利用,在有限的堆空间内让Young GC和Full GC或者是停顿时间达到一个相对平衡。而不能遇到问题就通过新增堆大小的方式来解决。

至于第二个问题,这里使用PS,是因为该应用本身就使用的PS垃圾回收器。基于同一个垃圾处理器来操作,更能体现调优带来的效果。如果换成CMS,也许就不是很好对比了,因为CMS下基本不存在Full GC。

四、惯例

如果你对本文有任何疑问或者高见,欢迎添加公众号lifeofcoder共同交流探讨。