FullGC产生的条件,定位方法以及解决策略
产生的条件
调用System.gc()方法
System.gc()方法会告诉JVM需要启动一次fullGC,但是JVM自己判断什么时候开始启动这次fullGC。
在FullGC的时候,会Stop The World。可以通过配置-XX:+DisableExplicitGC来禁用System.gc().
堆内存空间不足
预先将堆空间设置为20M,然后新生代大小设置为10M,执行如下的程序触发GC。
JVM 参数如下:
1 | -Xms20m |
每次生成10M的数据,循环100次
1 | public static void main(String[] args) { |
这样就会触发FullGC,并且导致堆内存溢出
GC日志是:
可以看出虚拟机做了两次FullGC,但是回收之后,内存空间还是不够,导致溢出
永久代空间不足
永久代在Java8中已经用元空间替换了。永久代代表空间Perm Gen。它主要用于存储常量池以及虚拟机加载的类元数据信息。
元空间不足
元空间在JDK 8之后出现,作为永久代的替代者。元数据空间没有直接使用JVM的内存,它用的是操作系统的内存,只有在操作系统的内存也被占满之后,元空间才会OOM
老年代空间不足
当新生代的对象经过多次垃圾回收之后,准备移入老年代时,老年代的空间不够,就会催发一次FullGC。
内存担保机制失败
在JVM发生minor gc之前,JVM会先判断老年代的最大可用的连续空间是否大于新生代所有对象的总空间。
如果大于的话,则minor GC是安全的,进行一次minor GC
如果小于的话,则判断虚拟机的HandlePromotionFailure参数,
如果为true,说明JVM允许担保失败,会继续检测老年代最大可用的连续空间>历次晋升到老年代对象的平均大小。若大于,将尝试进行一次minor gc,若失败,则重新进行一次full gc。
如果为false,则不允许毛线,需要进行Full GC
CMS GC回收出现promotion failed或concurrent mode failure
promotion failed:在 Minor GC 过程中,Survivor Unused 可能不足以容纳 Eden 和另一个 Survivor 中的存活对象, 那么多余的将被移到老年代, 称为过早提升(Premature Promotion)。 这会导致老年代中短期存活对象的增长, 可能会引发严重的性能问题。 再进一步, 如果老年代满了, Minor GC 后会进行 Full GC, 这将导致遍历整个堆, 称为提升失败(Promotion Failure)。
concurrent mode faulure:在CMS的并发标记和并发清理阶段,用户线程还是在继续运行,伴随着新的垃圾对象不断产生,但,这一部分垃圾对象是出现在标记过程结束以后,无法在档次收集中处理掉。另外,因此清理是和用户线程一起执行的,那就必须预留一定的空间给用户线程。如果预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败”。
定位方法
当系统出现频繁的响应超时或者CPU100时,可以怀疑下是否频繁FullGC导致系统响应慢且狂消耗资源了。
可以通过GC日志分析FullGC的频次;
也可以通过JVM自带的工具,jstat,或者阿里的Arthas
解决策略
禁用System.gc方法,JVM启动时设置参数+DisableExplicitGC
针对老年代空间不足的问题,尽量做到使对象在新生代就被回收,而不是等到到了老年代之后才被回收。然后根据业务场景分配合适的老年代空间大小。
针对永久代(JDK8之前),可以适当增大永久代的空间,或者在类Class信息不再使用的时候,即使卸载。
采用CMS,那么可以设置进行 n 次 CMS 后进行一次压缩式 Full GC,参数如下:
-XX:+UseCMSCompactAtFullCollection:允许在 Full GC 时,启用压缩式 GC
-XX:CMSFullGCBeforeCompaction=n 在进行 n 次,CMS 后,进行一次压缩的 Full GC,用以减少 CMS 产生的碎片。