#jvm参数配置
-Xms25m #堆内存最小25m
-Xmx25m #堆内存最大值25m
-Xmn10m #年轻代大小
-Xss1m #线程栈大小
-XX:+UseParNewGC #使用ParNew垃圾回收器
-XX:+PrintGCDetails #打印GC信息
-XX:+PrintGCDateStamps # 打印gc时间戳
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); }
}
# 当前单位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的总耗时
从上面图中可以得出以下结论
1.垃圾回收后,S1U变大,EU变小,EC执行GC后剩余的对象进入了S1U区
只需要调整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); }
}
从上图可以看到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] # 总耗时
原因是因为jvm堆内存大小,对象本身数据外还有结构内存和对象描述占用内存,特别容易出现动态年龄规划和大对象问题。所以这里调整了堆内存大小
-Xms256m
-Xmx256m
-Xmn100m
-Xss1m
-XX:+UseParNewGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:SurvivorRatio=8
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];
}
}
}
大约每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]
从下图中可以看到的变化:
1.YGC后Survivor比以往保留下来的对象都要小很多,这说明有一部分一直被保留的对象,不再被保留在Survivor区
2.OU突然从0变为1480.3,这说明在这次YGC后,有对象进入了老年区,而 此时恰好,YGC的次数是第16次GC时发生了变化。
同章节5.1配置一致
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+"次创建对象");
}
}
1.可以看到每次YGC后,Survivor区都在变大,每次大约是700kb的样子
2.在第五次GC后,我们可以发现Survivor突然变小,OU突然变大。即出现部分对象进入老年区的现象
3.survivor总内存是10M,当survivor的内存超过50%(6376.6kb),此时再GC往Survivor添加对象时,就触发了动态年龄判断规则,从而会移动一部分对象到老年代去,而不是全部到老年代。