JVM年轻代GC实验-晋升老年代多种情况

前提

JVM年轻代GC实验-晋升老年代多种情况_第1张图片

1.年轻代GC实验-YGC进入Survivor区

1.1 JVM参数设置

#jvm参数配置
-Xms25m #堆内存最小25m
-Xmx25m #堆内存最大值25m
-Xmn10m #年轻代大小
-Xss1m  #线程栈大小
-XX:+UseParNewGC #使用ParNew垃圾回收器
-XX:+PrintGCDetails #打印GC信息
-XX:+PrintGCDateStamps # 打印gc时间戳

1.2 年轻代垃圾回收测试代码

public static void useParNewCollector1() throws InterruptedException {
        // 由于survivor内存总共只有1m,那么在gc的时候 应该要有局部变量引用的对象小于survivor的大小
        for (int i = 0; i < 1000; i++) {
            int[] arr = new int[20 * 1024];
            Thread.sleep(500);
            int[] arr1 = new int[20 * 1024];
            Thread.sleep(500);
            int[] arr2 = new int[20 * 1024];
            Thread.sleep(500);
            int[] arr3 = new int[20 * 1024];
            Thread.sleep(500); }
    }

1.3 内存变化信息监控

# 当前单位kb
S0C # survivor0区的大小
S1C # survivor1区的大小
SOU # survivor0区的使用大小
S1U # survivor1区已使用的大小
EC # eden的大小
EU # eden已使用大小
OC # old区的大小
OU # old区已使用大小
MC # 元数据区大小
MU # 元数据区已使用内存大小
CCSC # 当前可用压缩空间大小
CCSU # 当前已使用压缩空间大小
YGC # 系统迄今为止进行young gc的次数
YGCT # 进行Young GC的总时长
FGC # 系统迄今为止进行full gc的次数
FGCT # 系统迄今为止进行full gc的总耗时
GCT # 所有gc的总耗时

JVM年轻代GC实验-晋升老年代多种情况_第2张图片

从上面图中可以得出以下结论

1.垃圾回收后,S1U变大,EU变小,EC执行GC后剩余的对象进入了S1U区


2. 年轻代-GC后对象超过Survivor晋升老年代实验

只需要调整for循环里的,即在同一时间有多个对象处于被引用状态,然后进行YGC时,存活的对象直接大于Survivor区的大小,所以直接进入到了老年代中。

public static void useParNewCollector2() throws InterruptedException {
        // 年轻代回收进入老年代 : 年轻代回收后对象超过survivor区
        for (int i = 0; i < 1000; i++) {
            int[] arr = new int[300 * 1024];
            Thread.sleep(500);
            int[] arr1 = new int[300 * 1024];
            Thread.sleep(500);
            int[] arr4 = new int[300 * 1024];
            Thread.sleep(500);
            int[] arr3 = new int[300 * 1024];
            Thread.sleep(500); }
    }

JVM年轻代GC实验-晋升老年代多种情况_第3张图片
从上图可以看到Survivor区中完全没有没有存活对象,而是直接GC到老年代,最后老年代满后触发了full gc

# full gc日志
2021-04-28T15:43:32.516+0800: 
[GC (Allocation Failure) 2021-04-28T15:43:32.516+0800:  # gc的原因
[ParNew: 7360K->7360K(9216K), 0.0000176 secs] # 年轻代gc内存变化与耗时
2021-04-28T15:43:32.516+0800:
 [Tenured: 13784K->1784K(16384K), 0.0012703 secs]  #老年代GC内存变化与耗时
21144K->1784K(25600K),  # 堆内gc内存变化
[Metaspace: 218K->218K(4480K)], 0.0013383 secs] # 元数据区GC内存变化与耗时
 [Times: user=0.02  # 用户态CPU执行时间(不包含挂起)
sys=0.00,  #内核态CPU执行时间 (不包含挂起)
real=0.00 secs] # 总耗时

5. 年轻代-基于年龄晋升机制实验

5.1 调整jvm参数

原因是因为jvm堆内存大小,对象本身数据外还有结构内存和对象描述占用内存,特别容易出现动态年龄规划和大对象问题。所以这里调整了堆内存大小

-Xms256m
-Xmx256m
-Xmn100m
-Xss1m
-XX:+UseParNewGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:SurvivorRatio=8

5.2 测试代码

public static void useParNewCollector3() throws InterruptedException {
        // 在不触发动态年龄规划和不触发大对象的情况下,经过15次YGC后,存活超过15次的对象将进入老年代
        Thread.sleep(20000); // 这个睡眠是为了方便执行jstat命令
        for (int i = 0; i < 1000; i++) {
            // 400kb的toOldForAge常驻内存,当YGC超过15次时,toOld会进入老年代
            // toOldForAge对象大小肯定是比400kb要大的,400kb是纯粹的数据的大小
            int[] toOldForAge = new int[100 * 1024];
            for (int j = 0; j < 1000; j++) {
                Thread.sleep(100);
                // 一个int占4个字节,数组纯数据大小为 4*200*1024 = 800kb大小 大约0.8兆
                // 年轻代大小为100m,按照8:1:1的比例,那么80m的内存最多能存102个arr对象
                int[] arr = new int[200 * 1024];
            }
        }
    }

5.3 gc日志

大约每10秒执行一次YGC

2021-04-29T15:57:03.116+0800: [GC (Allocation Failure) 2021-04-29T15:57:03.116+0800: [ParNew: 81126K->1877K(92160K), 0.0016310 secs] 81126K->1877K(251904K), 0.0016881 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2021-04-29T15:57:13.286+0800: [GC (Allocation Failure) 2021-04-29T15:57:13.286+0800: [ParNew: 83451K->2471K(92160K), 0.0011861 secs] 83451K->2471K(251904K), 0.0012624 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
2021-04-29T15:57:23.456+0800: [GC (Allocation Failure) 2021-04-29T15:57:23.456+0800: [ParNew: 84061K->2506K(92160K), 0.0013024 secs] 84061K->2506K(251904K), 0.0013823 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

5.4 jstat 内存变化信息

从下图中可以看到的变化:

1.YGC后Survivor比以往保留下来的对象都要小很多,这说明有一部分一直被保留的对象,不再被保留在Survivor区

2.OU突然从0变为1480.3,这说明在这次YGC后,有对象进入了老年区,而 此时恰好,YGC的次数是第16次GC时发生了变化。

JVM年轻代GC实验-晋升老年代多种情况_第4张图片


6.年轻代-基于动态年龄晋升实验

6.1 JVM配置

同章节5.1配置一致

6.2 实验代码

1.在这里使用temp来作为年龄累计工具

2.基于多次调整,使用85次循环为一轮的原因是,每85次左右就会执行一次gc

3.为了构建多年龄段存活对象引入了temp来对各年龄段保活

publicstatic voiduseParNewCollector4()throwsInterruptedException {
// 1.survivor超过50%
    // 2.survivor中存活次数从低到高累计,在累计到n时,超过survivor的50%
    // 3.存活次数大于n次的对象,都将晋升老年代
Thread.sleep(20000);
//年龄累计工具
int[][] temp =new int[9][];

// 400kb的toOldForAge常驻内存,当YGC超过15次时,toOld会进入老年代
// toOldForAge对象大小肯定是比400kb要大的,400kb是纯粹的数据的大小
inti = 0;
int[] toOldForAge =new int[100 * 1024];
for(intj = 0; j < 1000; j++) {
        Thread.sleep(100);
//一个int占4个字节,数组纯数据大小为 4*200*1024 = 800kb大小 大约0.8兆
//年轻代大小为100m,按照8:1:1的比例,那么80m的内存最多能存102个arr对象
int[] arr =new int[200 * 1024];
if(j%85 == 0 && i<9){
            temp[i++] = arr;
        }
        System.out.println("第"+j+"次创建对象");
    }
}

6.3 jstat 内存变化分析

1.可以看到每次YGC后,Survivor区都在变大,每次大约是700kb的样子

2.在第五次GC后,我们可以发现Survivor突然变小,OU突然变大。即出现部分对象进入老年区的现象

3.survivor总内存是10M,当survivor的内存超过50%(6376.6kb),此时再GC往Survivor添加对象时,就触发了动态年龄判断规则,从而会移动一部分对象到老年代去,而不是全部到老年代。

JVM年轻代GC实验-晋升老年代多种情况_第5张图片

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