前言
今天上线一个项目的时候在日志里发现项目启动的时候频繁GC,花了点时间分析了一下并且调整了一下JVM参数。
症状
项目启动
1 | java -server -Xmx1024m -XX:MaxDirectMemorySize=512M -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.txt -jar xxxxxxxx.jar |
gc.txt
好家伙,一启动就来9次 Young GC ,2次 Full GC。
现在先用jinfo看看配置参数
注意几个重要参数:1
2
3-XX:InitialHeapSize=62914560 //堆初始值
-XX:MaxHeapSize=1073741824 //堆最大值
-MaxDirectMemorySize=536870912 //虚拟机外最大内存
优化
Full GC
先来分析这两次Full GC以及它们前面的Young GC
1 | 2.536: [GC (Metadata GC Threshold) [PSYoungGen: 19883K->3584K(128512K)] 27143K->12014K(169472K), 0.0158155 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] |
可以看到两次Full GC都是由于Metadata GC Threshold造成的。我这里用的是JDK8,参数里没有明确指定metaspace的初始值和上限,这个时候初始值应该是默认的 21M 。问题不大,那我就把它的初始值和上限调大一点吧,调到64M。
措施:
追加元空间初始值
1 | java -server -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=64m -Xmx1024m -XX:MaxDirectMemorySize=512M -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.txt -jar xxxxxxxx.jar |
这个时候再查看gc.txt
可以看到已经没有Full GC了,不过还是有8次Young GC。
Young GC
其实从我的启动参数里面就可以看到,堆的容量我只指定了最大值(-Xmx1024m),并没有指定初始值。所以可以在配置参数里面看到 -XX:InitialHeapSize=62082048,即堆的初始容量只有60m左右。
而JVM默认的新生代和老年代空间占比为 1 : 3,所以新生代在一开始的时候只有15m的内存空间,因此JVM一开始在15m的时候就发生了Young GC。
所以现在要追加新生代和堆的初始值,新生代、老年代的空间占比我就不调了,使用默认的 1 : 3 即可,而堆的初始值调成1024m,这样新生代的初始值就是256m了。
措施:
追加堆的初始值
1 | java -server -Xms1024m -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=64m -Xmx1024m -XX:MaxDirectMemorySize=512M -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.txt -jar xxxxxxxx.jar |
现在再来看看gc.txt
可以看到只有两次Young GC了,这两次Young GC是在新生代对象准备超过256m的时候发生的……
好家伙,既然如此,那我也不讲武德了,我现在就把堆初始值和最大值都调成100G。
好吧,公司穷,只能买得起4G的服务器。
总结
这只是一次很简单的JVM启动参数优化,平时启动应用的时候还是得多注意注意堆的初始值和最大值配置,元空间这部分也不能忽略了。