Java OOM触发条件小记


前言

关于Java OOM的一些疑惑,对于GC回收和OOM触发条件的一些疑惑

简单说说GC

我们知道GC分为Minor GC和Full GC(Major GC另说),当eden区无法再分配空间给当前对象时,触发minorGC,而fullGC的触发条件就很多了,总结一下:

  1. 使用System.gc()

  2. 方法区无法分配对象

  3. Minor GC前,通过预测之前每次晋升老年代的平均大小(加权平均值)是否大于老年代的剩余空间大小,如果大于,直接full GC

  4. 对象默认分配到eden,如果对象过大(参数配置),则直接分配到老年代,同时大于老年代剩余大小,full GC
    上面说到,年轻代晋升老年代,大家都知道的是当在年轻代的年龄达到一定值(默认15)时,会晋升,但其实还有一种,就是内存空间分配担保

    内存空间分配担保

    这里我们先测试,然后看图说话(例子来自这里,但是改了一点):

    public class Test {
     private static final int _1MB = 1024*1024 ;
     static void test(){
         byte[] allocation1,
                 allocation2,
                 allocation3,
                 allocation4;
         allocation1 = new byte[2*_1MB];
         allocation2 = new byte[2*_1MB];
         allocation3 = new byte[_1MB];
    
         allocation4 = new byte[_1MB];
     }
    
     public static void main(String[] args) {
         test();
     }
    }

    运行参数: -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
    然后看GC日志

    可以看到,是当内存大小为7265K的时候触发了minorGC,但是按理说,6M的对象6144,eden大小8192K,不会触发的,
    但我们算一下,5M的对象是5120K,我们要算上一些内置对象和对象头的大小,当分配5M时,新生代已占7265K了,是无法分配剩下的1M的,
    然后我们看到结果,youngGen的变换是7265K -> 915K,而总的堆的变换是7562k->6044K,可以得到什么呢,GC开始时,eden大小是7265K,老年代大小是0K,GC后,新生代大小为915K,而老年代大小为6044-915 = 5129,就是我们那5M对象,
    fullGC将eden转为0(先不管那个fullGC),再看最后的heap属性,新生代大小是1106K,就是那1M,也就是说,之前的5M被晋升到了老年代,而当前的1M对象就分到了新生代.
    总结一下,当当前对象大于eden剩余空间,触发minorGC,然而survivor(from,to)无法放下活下来的对象,而剩余对象年龄也不到晋升年龄,所以只能老年代来承受,也就是把新生代的对象移到老年代,再分配新对象到eden.
    然后我们说说上面的full GC的事情,看下面这段程序:

    public class Test3 {
     private static final int _1MB = 1024*1024 ;
     static void test(){
         byte[] allocation1,
                 allocation2,
                 allocation3,
                 allocation4;
         allocation1 = new byte[2*_1MB];
         allocation2 = new byte[2*_1MB];
    
         allocation3 = new byte[2*_1MB];
    
     }
    
     public static void main(String[] args) {
         test();
     }
    }

    只是把两个_1MB合成一个,结果就不一样了,

    可以看到没有full GC了,有一篇博客有说到这个,根据这个,本人只能猜测一番,是GC调节暂停时间和吞吐量之间的一种平衡.

OOM测试

<深入理解Java虚拟机>测试用例

首先看程序

public class Test {
    static class OOMObject{

    }
    public static void main(String[] args) {
        List list = new ArrayList<>();
        while (true){
            list.add(new OOMObject());
        }
    }
}

运行的vm option :
-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

之所以这样设置,是为了更好的观察.然后看一下结果:

知道了前面的内存空间分配担保,就很好理解这个结果了,前两次GC都是MinorGC,都有第一次有内存空间担保,第二次Minor GC时已经无法担保,就有了Full GC协调,等youngGen和ParOldGen都无法再分配,就有了OOM.大概是这样一个过程.


  目录