目标:
了解GC过程中 threshold值的变化原因
步骤:
1、跑一个demo2、设置好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){
}
}
运行结果
我这里是占用了 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打印信息
发现他真的占满了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];
}
}
运行结果:
解析:发生
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];
}
}
运行结果
分配流程: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];
}
}
实践结果