探究threshold值的变化

目标:

了解GC过程中 threshold值的变化原因


步骤:

1、跑一个demo
2、设置好JVM参数
3、不断实验,得出结果

一、编写一个测试类,先写一个空的main方法,并设置好JVM参数

JVM参数设置如下:

-verbose:gc
-Xmx200M
-Xms200M
-Xmn50M
-XX:+PrintGCDetails
-XX:TargetSurvivorRatio=60
-XX:+PrintTenuringDistribution
-XX:+PrintGCDateStamps
-XX:MaxTenuringThreshold=6
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC

空跑一次main函数来查看java服务本身占用的空间大小

public class TestYGC {
    public static void main(String[] args){
	}
}

运行结果

探究threshold值的变化_第1张图片

我这里是占用了 4M。所以40 - 4 = 36,所以说我们还需要36M 才能把Eden区占满

为了达到TargetSurvivorRatio(期望占用的Survivor区域的大小)这个比例指定的值, 即5M*60%=3M(Desired survivor size)

我使用三个 1M的数组来占用此空间

public class TestYGC {
    public static void main(String[] args){
     	byte[] a1 = new byte[1 * 1024 * 1024];
        byte[] a2 = new byte[1 * 1024 * 1024];
        byte[] a3 = new byte[1 * 1024 * 1024];
	}
}

再整一个函数来申请空间,方便我们待会占满Eden区

	public static void makeGarbage(int size){
        byte[] a = new byte[size * 1024 * 1024];
    }

接下来占满Eden区试试

public class TestYGC {
    public static void main(String[] args){
     	byte[] a1 = new byte[1 * 1024 * 1024];
        byte[] a2 = new byte[1 * 1024 * 1024];
        byte[] a3 = new byte[1 * 1024 * 1024];
		
		makeGarbage(33);
	}
	public static void makeGarbage(int size){
        byte[] a = new byte[size * 1024 * 1024];
    }
}

启动程序,查看GC打印信息

探究threshold值的变化_第2张图片

发现他真的占满了Eden区,好,接下来触发YGC

再次申请一个数组,因为Eden已经满了,所以这里会触发Minor GC

public class TestYGC {
    public static void main(String[] args){
        byte[] a1 = new byte[1 * 1024 * 1024];
        byte[] a2 = new byte[1 * 1024 * 1024];
        byte[] a3 = new byte[1 * 1024 * 1024];

        makeGarbage(33);
        // 第一次Eden区沾满

        // 触发YGC
        byte[] a4 = new byte[1*1024*1024];

    }
    public static void makeGarbage(int size){
        byte[] a = new byte[size * 1024 * 1024];
    }
}

运行结果:

探究threshold值的变化_第3张图片

解析:发生 Allocation Failure 即内存分配失败

此时Eden区的占用为3%,因为 40*0.03 = 1.2 其实就是我们分配的 a4 那个对象内存

survivor的 from区 占用了73%,5*0.73 = 3.65,差不多是我们上面分配的 a1、a2、a3对象

分配流程:Eden区占满时,包含了a1、a2、a3 以及一个33M的a数组,当我们声明了一个 1M的a4数组时,JVM发现Eden区不够分配对象了,所以进行YGC,将 33M的a数组内存回收,将a1、a2、a3 移动至 survivor的 from区 ,此时a1、a2、a3 对象的年龄初始化为1,并将a4对象分配至Eden区。

此时还有一个需要注意的点:

就是Desired survivor size 3145728 bytes, new threshold 1 (max 6)

为什么threshold 为1 呢?

因为当前Survivor的from区对象占用超过了60%,这个值是我们设置的JVM参数,于是会重新计算对象晋升的age,min(age, MaxTenuringThreshold) = 1

我们再让他产生YGC

package com.csnz;

public class TestYGC {
    public static void main(String[] args){
        byte[] a1 = new byte[1 * 1024 * 1024];
        byte[] a2 = new byte[1 * 1024 * 1024];
        byte[] a3 = new byte[1 * 1024 * 1024];

        makeGarbage(33);
        // 第一次Eden区沾满

        // 触发YGC
        byte[] a4 = new byte[1*1024*1024];

        // 此时 Eden区占了3% 我们再让他产生YGC
        makeGarbage(38);
        byte[] a5 = new byte[1*1024*1024];

    }
    public static void makeGarbage(int size){
        byte[] a = new byte[size * 1024 * 1024];
    }
}

运行结果

探究threshold值的变化_第4张图片

分配流程:Eden区占满时,包含了a4 以及一个38M的a数组,当我们声明了一个 1M的a5数组时,JVM发现Eden区不够分配对象了,所以进行YGC,将 38M的a数组回收,将a4移动至 survivor的 from区 ,以及本来在from区的 a1、a2、a3 对象,但是由于我们之前的案例导致现在晋升对象的年龄是1岁,所以 a1、a2、a3 对象 符合晋升条件,会直接去到Old区,from区剩下 a4对象

那为什么threshold 现在又变成6了呢

因为from区动态的调整,现在里面只有一个1M的a4对象,占用没有达到60%(-XX:TargetSurvivorRatio=60),所以恢复到我们设置的初始值6!

假想:那我是不是第二次GC时,也让from区 占用超过60%,就能使它的threshold 变为1呢?

实践:在第一次YGC使,a4给他设置为3M,再用垃圾数组填充满Eden区,然后a5给它1M,让他触发YGC

package com.csnz;

public class TestYGC {
    public static void main(String[] args){
        byte[] a1 = new byte[1 * 1024 * 1024];
        byte[] a2 = new byte[1 * 1024 * 1024];
        byte[] a3 = new byte[1 * 1024 * 1024];

        makeGarbage(33);
        // 第一次Eden区沾满

        // 触发YGC
        byte[] a4 = new byte[3*1024*1024];

        // 此时 Eden区占了3% 我们再让他产生YGC
        makeGarbage(36);
        byte[] a5 = new byte[1*1024*1024];

    }
    public static void makeGarbage(int size){
        byte[] a = new byte[size * 1024 * 1024];
    }
}

实践结果

探究threshold值的变化_第5张图片

噢耶,探究后总算明白了 threshold值的变化规则

你可能感兴趣的:(JVM,java,JVM)