JVM配置是这样的: jre1.8, 堆的最大空间是3G,线程执行栈的大小是256K,新生代的大小是1G,老年代的大小是2G.如下图:
结果在日志发现了这个错误:
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
这个错误产生的原因是,jvm在进行gc的时候, 使用大于98% 以上的时间去释放小于 2% 的heap空间时,才会报这个异常。
这其实是jvm预判将会发生OutOfMemery异常,就提早抛出这个异常。并不代表jvm没有内存空间了。
于是在tomcat的启动参数里,加入-XX:-UseGCOverheadLimit参数,关闭jvm的预判功能。
再观察,发现没过多久,就发生了java.lang.OutOfMemoryError异常,同时发现年轻代的GC非常频繁(如下图),而且年轻代的内存还有很多剩余空间就发生了GC。怀疑业务代码里执行了System.gc(), 人工触发GC。
我让那个朋友扫描代码,果然发现了System.gc()。于是在tomcat启动参数里加入-XX:+DisableExplicitGC,表示关闭人工GC。意思是,即使在业务代码里有System.gc()的调用,也不会执行GC,相当于执行了一个空调用。
重新启动tomcat再观察:年轻代的GC明显少了,而且每次都是到年轻代的内存空间没有了才GC。这就正常了。
但是这个问题还没有被真真解决,GC正常了,但是还是会发生OutOfMemoryError异常。通过使用jstat -gc pid 3s观察,还会发现:新生代的S1使用空间一直是0,即使发生GC也是0.这说明了两个问题:
1.S1分配了内存,但是一直没有被使用,简直就是浪费。
2.新生代发生GC的时候,Eden和S0的存活对象的总大小,要大于S1的大小。
下图是Heap的分配情况:
为了一次性解决问题,我把堆得大小设置为5G,新生代3G,老年代2G。同时,扩大S0和S1的大小。最后的参数是这样的:
-XX:-UseGCOverheadLimit -XX:+DisableExplicitGC -Xms5G -Xmx5G -XX:NewSize=3G -XX:MaxNewSize=3G -XX:SurvivorRatio=3