前言
关于Java OOM的一些疑惑,对于GC回收和OOM触发条件的一些疑惑
简单说说GC
我们知道GC分为Minor GC和Full GC(Major GC另说),当eden区无法再分配空间给当前对象时,触发minorGC,而fullGC的触发条件就很多了,总结一下:
使用
System.gc()
方法区无法分配对象
Minor GC前,通过预测之前每次晋升老年代的平均大小(加权平均值)是否大于老年代的剩余空间大小,如果大于,直接full GC
对象默认分配到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.大概是这样一个过程.