《深入理解java虚拟机》学习-第三章-内存分配策略

1.java自动内存管理:
① 给对象分配内存;
② 回收分配给对象的内存;


2.内存分配
① 在堆上分配(也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配);
② 对象主要分配在新生代的Eden区上;
③ 若启动了本地线程分配缓冲,将按线程优先在TLAB上分配;
④ 少数情况下也可能会直接分配在老年代中;
⑤ 分配细节取决于哪一种垃圾收集器组合以及虚拟机中内存相关配置参数;


3.对象优先在Eden上分配
① 大多数情况下,对象在新生代Eden空间分配;
② Eden空间不足,虚拟机发起一次Minor GC;
③ Minor GC:指发生在新生代的垃圾收集动作,因为大部分java对象都具备朝生夕灭的特性,所以执行非常频繁,一般回收速度也快;
④ Major GC/Full GC:老年代GC,经常会伴随至少一次的Minor GC(非绝对,如Parallel Scavenge收集器),速度一般比Minor慢10倍以上;
⑤ 作者测试程序如下:
package com.demo.test;


public class testAllocation {


    private static final int _1MB = 1024*1024;


    /**
     * VM参数:限制java堆大小为20M,其中10M分配给新生代,剩下10M给老年代,
     * -verbose:gc     -Xms20M     -Xmx20M     -Xmn10M
     * -XX:+PrintGCDetails  -XX:SurvivorRatio=8 (新生代中Eden区:Survivor区=8:1)
     */


    public static void test(){
        byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1 = new byte[2*_1MB];
        allocation2 = new byte[2*_1MB];
        allocation3 = new byte[2*_1MB];


        //此时1,2,3对象都存活,但不足以分配4,出现一次Minor GC,
        //GC时发现3个2M的无法放入Survivor空间(它只有1M大小),所以通过分配担保机制提前转移到老年代
        //结果是Eden占用4M-被allocation4占用,Survivor空闲,老年代占用6M(被1,2,3占用)
        allocation4 = new byte[4*_1MB];
    }


}




4.大对象直接进入老年代
① 分配对象:需要大量连续内存空间的java对象,如长字符串以及数组(byte[]等);
② 避免“短命大对象”;
③ 虚拟机参数:如 -XX:PretenureSizeThreshold=3145728,意思是超过3M的对象都在老年代直接进行分配;
④ 作者测试程序如下:
package com.demo.test;


public class TestPretenureSizeThreshold {
    
    private static final int _1MB = 1024*1024;


    /**
     * VM参数:限制java堆大小为20M,其中10M分配给新生代,剩下10M给老年代,
     * -verbose:gc     -Xms20M     -Xmx20M     -Xmn10M
     * -XX:+PrintGCDetails  -XX:SurvivorRatio=8 (新生代中Eden区:Survivor区=8:1)
     * -XX:PretenureSizeThreshold=3145728
     */
    
    public static void testPretenureSizeThreshold(){
        byte[] allocation;
        allocation = new byte[4*_1MB];  //直接分配在老年代
    }
    
}




5.长期存活的对象将进入老年代
① 对象年龄计数器:自对象在Eden出生,经过第一次Minor GC后仍存活,且能被Survivor容纳,将被移动到Survivor空间,对象年龄为1岁。以后每在Survivor熬过一次Minor GC,年龄就长大1岁,默认长到15岁后,晋升到老年代。
② 晋升年龄阈值设置:-XX:MaxTenuringThreshold=15参数,默认15岁;
③ 作者测试程序如下:
package com.demo.test;


public class TestTenuringThreshold {


    private static final int _1MB = 1024*1024;


    /**
     * VM参数:限制java堆大小为20M,其中10M分配给新生代,剩下10M给老年代,
     * -verbose:gc     -Xms20M     -Xmx20M     -Xmn10M
     * -XX:+PrintGCDetails  -XX:SurvivorRatio=8 (新生代中Eden区:Survivor区=8:1)
     * -XX:MaxTenuringThreshold=1 (先用1测试,在换其他数值测试)
     * -XX:+PrintTenuringDistribution
     */
    
    public static void testTenuringThreshold(){
        byte[] allocation1,allocation2,allocation3;
        allocation1 = new byte[_1MB/4];
        
        //什么时候进入老年代取决于XX:MaxTenuringThreshold设置
        allocation2 = new byte[4*_1MB];
        allocation3 = new byte[4*_1MB];
        allocation3 = null;
        allocation3 = new byte[4*_1MB];
    }
}




6.动态对象年龄判断
① 定义:不是必须达到MaxTenuringThreshold才能晋升老年代,若Survivor中相同年龄多有对象大小的总和大于Survivor空间一半,年龄大于或等于该年龄的对象就可以直接进入老年代
② 作者测试程序如下:
package com.demo.test;


public class TestTenuringThreshold2 {


    private static final int _1MB = 1024*1024;


    /**
     * VM参数:限制java堆大小为20M,其中10M分配给新生代,剩下10M给老年代,
     * -verbose:gc     -Xms20M     -Xmx20M     -Xmn10M
     * -XX:+PrintGCDetails  -XX:SurvivorRatio=8 (新生代中Eden区:Survivor区=8:1)
     * -XX:MaxTenuringThreshold=15 
     * -XX:+PrintTenuringDistribution
     */
    
    public static void testTenuringThreshold2(){
        byte[] allocation1,allocation2,allocation3,allocation4;
        allocation1 = new byte[_1MB/4];
        allocation2 = new byte[_1MB/4];     //此时allocation1和allocation2大于等于Survivor空间(1M)一半
        
        allocation3 = new byte[4*_1MB];
        allocation4 = new byte[4*_1MB];
        allocation4 = null;
        allocation4 = new byte[4*_1MB];
    }
    
}




7.空间分配担保
① Minor GC前,VM先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,若成立,那么Minor GC可以确保是安全的;
② 若不成立,VM查看HandlePromotionFailure设置值是否允许担保失败;
③ 若允许失败,那么继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,若大于,将尝试进行一次Minor GC,尽管此次Minor GC是有风险的;若小于或设置不允许毛线,那这时也要改为进行一次Full GC;
④ “冒险”:Survivor空间在Minor GC后仍持有大量或全部对象(过于极端),需要老年代分配担保,把Survivor无法容纳的对象进入老年代,但老年代并不知道会有多少对象存活下来,所以会根据历次晋升到老年代对象容量的平均大小值作为经验值,与老年代剩余空间比较,决定是否进行Full GC来让老年代腾出更多空间;
⑤ 上面的取平均值是很冒险的,因为有时可能会很大,但大部分情况下都还是会将HandlePromotinFailure开关打开,避免Full GC过于频繁;
⑥ JDK1.6 Update 24后在代码中已不会再使用HandlePromotionFailure参数,规则变为执拗老年代的持续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC,否则进行Full GC;

你可能感兴趣的:(Java)