JVM-GC

JVM GC(jdk1.7)

一、minor gc

​ 发生在Eden、From、To之间,垃圾对象清理,存活对象从【Eden、From】复制到To,或者【Eden、To】复制到From

image-20200514094712525

image-20200514095435288

image-20200514095503930

晋升老年代

​ 在Survivor存活的对象,一般情况下(注意这里用词),年龄达到一定阈值,将晋升到老年代,阈值通过-XX:MaxTenuringThreshold设置,默认15,例如-XX:MaxTenuringThreshold=2

image-20200514101549547

大对象

​ -XX:PretenureSizeThreshold 大于这个设置值的对象可以直接在老年代分配对象

​ 这样做的好处是:避免在Eden区及两个Survivor区之前发生大量的内存复制

image-20200514101747860

动态对象年龄判定

​ 前面说关于GC年龄时用词是一般情况下,这里讲讲特殊情况

​ 并不一定要等到-XX:MaxTenuringThreshold,对象才进入老年代,在Survivor区相同的对象且年龄相同,总和大于Survivor的一半,也可以直接进入老年代

​ 下例:

Dog1和Dog2年龄一样,且Dog1和Dog2年龄大于Survivor的一半,将直接进入老年代

image-20200514102039391

GC 日志

  • 例1

    survivor不够复制,直接进入老年代

package com.test.allocation;

/**
 * @author mdl
 * @date 2020/5/13 15:40
 */
public class TestAllocation {

    private static final int _1MB = 1024 * 1024;

    /**
     * VM 参数: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
     * Java堆大小20M,不可扩展,年轻代10M,年老代10M(Eden:8M, From:1M, To:1M)
     */
    public static void testAllocation(){
        byte[] allocation1;
        byte[] allocation2;
        byte[] allocation3;
        byte[] allocation4;

        allocation1 =new byte[2* _1MB];
        allocation2 =new byte[2* _1MB];
        allocation3 =new byte[2* _1MB];
        // 此时Eden已分配了6M,剩余2M不足以分配allocation4,触发Minor GC
        // GC期间,试图将存活的3个2M的对象复制到Survivor区,但是Survivor空间只有1M
        // 这时通过分配担保机制提前转义到老年代
        allocation4 =new byte[4* _1MB];
        // 所以此次GC结束:4M的allocation4进入Eden区,Survivor区空闲,老年代被占用6M

    }


    public static void main(String[] args) {
        TestAllocation.testAllocation();
    }

}

image-20200514102632928

  1. 分配allocation4时,发现Eden已经分配了6M(Eden有8M空间),不足以分配allocation4,发生minor gc

  2. gc期间,针对Eden,3个2M对象(allocation1、2、3)无法在Survivor分配,这时通过分配担保进入老年代

    ,老年代占用空间为6M

  3. 新生代资源利用为eden+from = 8192K + 1024K= 9216K,即9M,腾出了4M给allocation4利用

  • 例2

    大对象直接进入老年代

    package com.test.allocation;
    
    /**
     * 大对象直接进入老年代
     *
     * @author mdl
     * @date 2020/5/13 15:40
     */
    public class TestPretenuerSizeThreshold {
    
        private static final int _1MB = 1024 * 1024;
    
        /**
         * VM 参数: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
         * Java堆大小20M,不可扩展,年轻代10M,年老代10M(Eden:8M, From:1M, To:1M)
         */
        public static void testAllocation(){
            byte[] allocation =new byte[4* _1MB];
        }
    
    
        public static void main(String[] args) {
            TestPretenuerSizeThreshold.testAllocation();
        }
    }
    

    image-20200514103215715

    ​ 从GC日志上看出,老年代的占用达到40%,总共10M,使用了4M,可以知道4M的allocation对象直接进入了老年代,这是因为PretenureSizeThreshold参数被设置为3MB

  • 例3

    相同的对象且年龄相同,空间利用率大于Survivor(From或To)的一半,gc时进入老年代(这其实是个误区)

    package com.test.allocation;
    
    /**
     * 动态对象年龄判定
     *
     * @author mdl
     * @date 2020/5/13 15:40
     */
    public class TestTenuringThreshold {
    
        private static final int _1MB = 1024 * 1024;
    
        /**
         * VM 参数: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution
         * Java堆大小20M,不可扩展,年轻代10M,年老代10M(Eden:8M, From:1M, To:1M)
         */
        public static void testAllocation() {
            byte[] allocation1 = new byte[_1MB / 4];
            // allocation1+ allocation2 大于survivor空间的一半了
            byte[] allocation2 = new byte[_1MB / 4];
    
            byte[] allocation3 = new byte[4 * _1MB];
            // 第一次Minor GC allocation1和allocation2年龄为1
            byte[] allocation4 = new byte[4 * _1MB];
            // 将allocation4置空,在下一次回收将被回收
            allocation4 = null;
            // 第二次Minor GC allocation1和allocation2进入老年代
            allocation4 = new byte[4 * _1MB];   // 1
        }
    
    
        public static void main(String[] args) {
            TestTenuringThreshold.testAllocation();
        }
    
    }
    
    
    

    如果注释掉代码1处

    image-20200514105026730

    分析:

    只发生一次minor gc,allocation4分配前,eden已占用1/2 M+ 4M=4.5M,还剩1.5M,不足以分配allocation4

    allocation3进入老年代,allocation1、2进入from,eden分配allocation4

    至于为什么结果from是100%,暂时未可知

    释放代码1处

    image-20200514111557614

    survivor空闲,而老年代空间增加了10%,allocation1、2进入了老年代了

    当然以上推断【对象相同且年龄相同,空间大约survivor一半】其实是有问题的

    我们假如:

    1. MaxTenuringThreshold为15

    2. 年龄1对象30%

    3. 年龄2对象30%

    4. 年龄3对象40%

      可以看到survivor已经100%了,但是对象就不晋升,导致老年代有空间,对象就停留在年轻代,这明显与jvm的表现不符。

      uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
      	//survivor_capacity是survivor空间的大小,TargetSurvivorRatio:目标存活率,默认50
        size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
        size_t total = 0;
        uint age = 1;
        while (age < table_size) {
          total += sizes[age];//sizes数组是每个年龄段对象大小
          if (total > desired_survivor_size) break;
          age++;
        }
        uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
      	...
      }
      

      代码的意思大概总结为:

      从age为1开始,累加每一个年龄段对象大小,如果大小超过survivor空间一半,取age与MaxTenuringThreshold的最小值暂记为result,大于等于result的age将进入老年代

      年龄1(30%)+年龄2(30%)> 50%,那么大于等于年龄2的将晋升到老年代

      参考:

      https://www.cnblogs.com/lovellll/p/10246073.html

      《深入理解Java虚拟机》

每一步脚印都要扎得深一点!
原文地址:https://www.cnblogs.com/bloodthirsty/p/12887487.html