JVM出现频繁Full GC

时间:2024-10-16 17:37:55
java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor948.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.xxl.job.core.handler.impl.MethodJobHandler.execute(MethodJobHandler.java:31)
at com.xxl.job.core.thread.JobThread.run(JobThread.java:163)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

Full GC,全称Full Garbage Collection,翻译成中文就是“完全垃圾回收”。它会清理堆内存中所有分代(新生代、老年代、永久代/元空间)里的无用对象。简单说,当JVM觉得“我这堆内存快被占满了”,它就会触发Full GC来释放内存空间。

Full GC的触发条件一般有以下几种:

1、老年代空间不足:当新生代对象过多,晋升到老年代,而老年代空间不足时,JVM就会触发Full
GC。这个情况比较常见,尤其在做批量操作、处理大对象时更容易发生。

2、永久代/元空间满了:如果使用的是JDK 7或者之前的版本,永久代满了也会触发Full GC。而在JDK
8以后引入了元空间(Metaspace)来替代永久代,这种情况相对较少,但依然有可能。

3、System.gc():某些代码里可能存在手动调用System.gc(),这就会强制触发Full
GC。这种情况你可能在某些“聪明”的开发者写的代码中见过,但实际项目中基本没人会这么干,因为它的副作用太大。

4、内存分配策略问题:有时候JVM在做内存分配时,为了安全起见,会触发Full
GC。比如:老年代剩余的连续内存空间小于新生代对象的总大小时,JVM可能会选择触发Full GC。

5、代码中有大对象:大对象直接进入老年代。比如那种“地图类”的业务对象,如果在短时间内频繁创建,老年代很容易被撑爆。

快速解决: 重启项目

通过以下几步来排查

1、启用GC日志

在JVM启动参数里加上: -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/path/to/gc.log
这段配置会把GC的详细信息打印到日志文件中。你可以在gc.log里查看Full GC发生的频率和原因。

查看内存分配情况

可以使用jmap命令: jmap -histo:live
是你的Java进程ID。这条命令会显示所有对象在内存中的分布情况,告诉你每种类型的对象占了多大空间

导出内存堆快照

jmap -dump:format=b,file=heapdump.hprof
把堆内存导出来,用工具(如Eclipse MAT)分析。通过分析内存快照,你可以找出哪些对象占用了最多的空间

监控老年代使用率

jstat -gcutil 1000
这会每隔1秒打印一次GC信息。你可以观察老年代(Old)的使用率是否过高。如果老年代经常在80%以上,可能就会频繁触发Full GC

调整JVM参数

可以通过调优JVM参数来减少Full GC发生的频率。常用参数有
-Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC 这里用G1收集器是因为它能更好地处理大内存场景,减少Full GC的发生频率。如果你用的是CMS或者Parallel GC,可以考虑切换到G1。

检查是否有大对象

代码中存在大对象,尽量避免在短时间内频繁创建
在这里插入图片描述

像这种大列表,一旦被创建,就直接晋升到老年代,容易导致Full GC。解决方案是拆分大对象,或者减少使用频率

增加堆内存

堆内存加大了,Full GC发生的频率自然就会下降。不过,现实中你可能会遇到客户嫌弃“加内存太贵”

避免手动调用 System.gc()

检查代码中是否存在手动调用System.gc()的地方,改掉就好了。如果项目中确实需要显式调用,可以考虑加上参数:
-XX:+DisableExplicitGC
这样就可以禁用显式的GC调用,防止因为误操作导致的Full GC。

使用更好的内存分配策略

比如使用-XX:MaxGCPauseMillis=200来控制每次GC的最大暂停时间。这个参数告诉JVM你希望每次GC的暂停时间不要超过200ms,从而减少Full GC的发生。