解决jvm GC过于频繁的问题

时间:2024-03-28 08:59:29

JVM配置是这样的: jre1.8, 堆的最大空间是3G,线程执行栈的大小是256K,新生代的大小是1G,老年代的大小是2G.如下图:

解决jvm GC过于频繁的问题

 

结果在日志发现了这个错误:

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。

解决jvm 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的分配情况:

解决jvm GC过于频繁的问题

为了一次性解决问题,我把堆得大小设置为5G,新生代3G,老年代2G。同时,扩大S0和S1的大小。最后的参数是这样的:

-XX:-UseGCOverheadLimit -XX:+DisableExplicitGC -Xms5G -Xmx5G -XX:NewSize=3G -XX:MaxNewSize=3G  -XX:SurvivorRatio=3