【JVM】gc实例深度解析(阈值,日志)

1.日志分析

public class GText1 {
	public static void main(String[] args) {
		int length = 1024*1024;
		System.out.println("GC触发时机如下:");
		
		byte[] a = new byte[2*length];
		byte[] b = new byte[3*length];
		byte[] c = new byte[3*length];
		System.out.println(c.length);
	}
}

设置参数:
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
gc详细信息 初始堆大小 最大堆大小 新生代大小 打印GC细节信息 新生代比例8 1 1(Eden区,两个Survivor区)
结果:

GC触发时机如下:
[GC (Allocation Failure) [PSYoungGen: 6105K->632K(9216K)] 6105K->5760K(19456K), 0.0031214 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 632K->0K(9216K)] [ParOldGen: 5128K->5635K(10240K)] 5760K->5635K(19456K), [Metaspace: 2559K->2559K(1056768K)], 0.0049341 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
3145728
Heap
 PSYoungGen      total 9216K, used 3313K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 40% used [0x00000000ff600000,0x00000000ff93c708,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 10240K, used 5635K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 55% used [0x00000000fec00000,0x00000000ff180ed0,0x00000000ff600000)
 Metaspace       used 2566K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 281K, capacity 386K, committed 512K, reserved 1048576K

分析:
[GC (Allocation Failure) [PSYoungGen: 6105K->632K(9216K)]
6105K->5760K(19456K),
触发一次ScavengeGC(分配失败)主要用于清理新生代,用PS垃圾收集器,因为新生代满了,其中年轻代中原存活对象所占据的空间6105K 执行完GC后存活632K,年轻带代9216K = 9M (之前设置参数时年强带让设置为10M,由于e和2s为8:1:1其中一个Survivor为空,所以可用总空间为9M

6105 - 632 = 5473 即为年清代释放的空间(完全释放+存入老年代)
6105K->5760K(19456K)表示总堆存活对象占用空间为6105k,执行完gc后5760K(新生代+老年代)
* 6105 - 5760 = 345 即整个堆完全释放掉的空间345k
* 5760 -632 = 5128k gc后总堆存活对象空间 - gc后新生代存活空间 = gc后老年代空间
* 5473 - 345 = 5128k 新生代释放空间 - 完全释放空间 =gc 后老年代空间
* 两种都能算出老年代剩余空间大小,且结果相同

[Full GC (Ergonomics) [PSYoungGen: 632K->0K(9216K)]
* [ParOldGen: 5128K->5635K(10240K)] 5760K->5635K(19456K),
* [Metaspace: 2559K->2559K(1056768K)], 0.0049963 secs]
* [Times: user=0.00 sys=0.00,
触发一次Full GC,清理新生代,老年代,元空间
* 对于新生代[PSYoungGen: 632K->0K(9216K)]全部清除
* [ParOldGen: 5128K->5635K(10240K)] 5760K->5635K(19456K),对于老年代触发gc前5128K,触发后5635K,老年代空间10M,可以看到gc后对象增多了,因为老年代中的对象本来就存活时间久,再加上从新生代传过来的对象,所以增多很正常
* 新生代释放632K包括完全释放和存入老年代,而老年代增加了:5635K-5128k=507k,所以632-507 = 125K,完全释放125k
* 总堆空间gc前存活对象占5760K,gc后5635K,5760-5635=125k,也证明了完全释放125k
* 元空间没变
稍微修改程序,将C改为4M,2+3+4 = 9 > 8M Eden区

public class GText1 {
	public static void main(String[] args) {
		int length = 1024*1024;
		System.out.println("GC触发时机如下:");
		
		byte[] a = new byte[2*length];
		byte[] b = new byte[3*length];
		byte[] c = new byte[4*length];
		System.out.println(c.length);
	}
}

参数不变,结果如下:

GC触发时机如下:
4194304
Heap
 PSYoungGen      total 9216K, used 6269K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 76% used [0x00000000ff600000,0x00000000ffc1f6a0,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 10240K, used 4096K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  object space 10240K, 40% used [0x00000000fec00000,0x00000000ff000010,0x00000000ff600000)
 Metaspace       used 2565K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 281K, capacity 386K, committed 512K, reserved 1048576K

个人理解为233加起来8M刚好把新生代的ed区域填满触发GC,而234已经超了ed的容纳范围,所以a,b占5M,放在ed里,当c来的时候ed区没有满,但是剩余空间不够了,所以直接把c放到老年代,从ParOldGen total 10240K, used 4096K 可以看出,老年代刚好就是4M也就是c的大小,故不会gc.

阈值

阈值:新生代中每次gc都会讲Survivor区复制到另一区域,然后这些对象岁数+1,当存活对象岁数达到阈值时该就会被送入老年代,阈值不是唯一定死参数,JVM会动态调整这一参数,以便处理不同场景下的工作。
乍一看这么做又不符合阈值这一设定,那这么做的意义是什么呢?
有一个问题,若存活对象都没达到阈值,但是占据很大Survivor空间,这时候由于复制清除算法,每次复制的量很大效率极低,而且剩余空间很少,这样就会出现Survivor空间不足导致一些新对象直接进入老年代,这样做年龄大的没进去,年龄小的先进去,彻底破坏阈值的功能,牵连较大
* 假设我们设置的阈值是5,结果经过几次gc后,年龄为2的对象占据整个Survivor空间的90%,由于没有达到阈值,那么接下来几次gc中这些
对象仍然占着位置,每次复制时工作量大,并且Survivor空间不足,不得已把新进入的Survivor对象直接送入老年代,这样子就失去阈值的意义

所以经历了多次GC后,存活的对象会在From Survivor与To Survivor之间来回存放,而这里面的一一个前提则是这两个空间有足够的大小来存放这些数据,在gc算法中,
会计算每个对象年险的大小,如果达到某个年龄后发现总大小已经大于了Survivor空间的50%,那么这时就需要调整阈值,不能再继续等到默认的5次GC后才完成晋升,因为这样会导致Survivor空间不足,所以需要调整阈值,让这些存活对象尽快完成晋升。也就是说,当年龄为2的对象占据超过50%的空间,gc就会修改阈值,并且把这批年龄为2的对象送入老年代,这样即解决了占用Survivor空间过大每次复制工作量过大的问题,又符合年龄大的先进去老年代的设定

实例分析:

public class GText2 {
	public static void main(String[] args) throws InterruptedException {
		byte[] by1  = new byte[1024*1024];
		byte[] by2  = new byte[1024*1024];
		
		Mygc();
		
		Thread.sleep(1000);
		System.out.println("11111111---------------------------");

		Mygc();
		
		Thread.sleep(1000);
		System.out.println("222222222-------------------------------");

		Mygc();
		
		Thread.sleep(1000);
		System.out.println("333333333--------------------------------");
		

		Mygc();
		
		Thread.sleep(1000);
		System.out.println("44444444-----------------------------------");
		
		byte[] by3  = new byte[1024*1024];
		byte[] by4  = new byte[1024*1024];
		byte[] by5  = new byte[1024*1024];
		
		Mygc();
		
		Thread.sleep(1000);
		System.out.println("55555555------------------------------------");
		
		Mygc();
		
		Thread.sleep(1000);
		System.out.println("66666666-------------------------------------");
		System.out.println("0K!!!");
	}
	private static void Mygc() {
		for(int i = 0 ; i < 40 ; i++) {
			byte[] by  = new byte[1024*1024];
		}
	}
}

设置参数:-verbose:gc -Xmx200M -Xmn50M
-XX:TargetSurvivorRatio=60 —— Survivor空间使用大小60%
-XX:+PrintTenuringDistribution ——打印年龄情况
-XX:+PrintGCDetails ——gc详细信息
-XX:+PrintGCDateStamps ——gc时间戳
-XX:+UseConcMarkSweepGC ——用CMS收集器
-XX:+UseParNewGC——ParNeW收集器
-XX:MaxTenuringThreshold=3
MaxTenuringThreshold作用:在可以自动调节对象晋升(Promote) 到老年代阈值的GC中,设置该阈值的最大值。
该参数的默认值为15,CMS中默认值为6, G1中默认为15 (在JVM中,该数值是由4个bit来表示的,所以最大值1111, 即15)

结果:

2020-03-24T18:56:23.360+0800: [GC (Allocation Failure) 2020-03-24T18:56:23.360+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age   1:    2626696 bytes,    2626696 total
: 40551K->2592K(46080K), 0.0014661 secs] 40551K->2592K(125952K), 0.0015310 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
11111111---------------------------
2020-03-24T18:56:24.365+0800: [GC (Allocation Failure) 2020-03-24T18:56:24.365+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age   1:        624 bytes,        624 total
- age   2:    2625856 bytes,    2626480 total
: 43332K->2840K(46080K), 0.0011817 secs] 43332K->2840K(125952K), 0.0012119 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
222222222-------------------------------
2020-03-24T18:56:25.370+0800: [GC (Allocation Failure) 2020-03-24T18:56:25.370+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age   1:        120 bytes,        120 total
- age   2:        624 bytes,        744 total
- age   3:    2625912 bytes,    2626656 total
: 43577K->2779K(46080K), 0.0007333 secs] 43577K->2779K(125952K), 0.0007638 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
333333333--------------------------------
2020-03-24T18:56:26.375+0800: [GC (Allocation Failure) 2020-03-24T18:56:26.375+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age   1:        128 bytes,        128 total
- age   2:        120 bytes,        248 total
- age   3:        624 bytes,        872 total
: 43517K->101K(46080K), 0.0019409 secs] 43517K->2677K(125952K), 0.0019954 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
44444444-----------------------------------
2020-03-24T18:56:27.381+0800: [GC (Allocation Failure) 2020-03-24T18:56:27.381+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 1 (max 3)
- age   1:    3145904 bytes,    3145904 total
- age   2:        128 bytes,    3146032 total
- age   3:        120 bytes,    3146152 total
: 40840K->3122K(46080K), 0.0009396 secs] 43416K->5699K(125952K), 0.0009708 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
55555555------------------------------------
2020-03-24T18:56:28.386+0800: [GC (Allocation Failure) 2020-03-24T18:56:28.386+0800: [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 3)
- age   1:        128 bytes,        128 total
: 43861K->6K(46080K), 0.0017031 secs] 46438K->5655K(125952K), 0.0017369 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
66666666-------------------------------------
0K!!!
Heap
 par new generation   total 46080K, used 13916K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  33% used [0x00000000f3800000, 0x00000000f4595978, 0x00000000f6000000)
  from space 5120K,   0% used [0x00000000f6000000, 0x00000000f6001980, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 79872K, used 5649K [0x00000000f6a00000, 0x00000000fb800000, 0x0000000100000000)
 Metaspace       used 2566K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 281K, capacity 386K, committed 512K, reserved 1048576K

挨个分析:
先分析第一个 Desired survivor size 3145728 bytes :说明survivor区为3M,这是因为总共新生代分了50M,按照811分,
ed区占据40M,两个survivor各占5M,上面又设置了survivor60%使用,所以结果为3M,每次调用方法产生40M的数组对象存入堆中,使得ed满触发gc

  • new threshold 3 (max 3):阈值为三

  • age 1: 2626696 bytes, 2626696 total 此时第一次gc,所以只有年龄为1的,2626664 bytes = 2M就是by1和by2两个存活

  • 40551K->2592K(46080K), 0.0013733 secs] 40551K->2592K(125952K)根据上次的分析,可以知道新生代存活从40551K变为2592K

  • (新生代共45M:end区40M+5M一个ser区),总堆空间从存活从40551K变为2592K(剩下的大概2M为by1和by2),

  • 总堆与新生代完全一致,这是因为根本没有达到阈值的,所以老年代为空

  • 第二次清理完后新增一批新的1岁,上次存活的两个by已经变为2岁了,由于仍然没有人达到阈值,所以总堆和新生代存活仍然完全一致

  • 第三次和第二次差不多,清理完后多了一批3岁的,总堆和新生代存活仍然完全一致

  • 第四次就不一样了,因为已经有达到阈值的对象,所以他就被送入老年代

  • 43517K->101K(46080K), 0.0024483 secs] 43517K->2677K(125952K),

  • 新生代剩余101K,总堆存活2677K :2881-305 = 2576k(老年代)

  • 43517K - 101K = 43416(新生代释放的:43416 = 完全释放+晋升老年) 总堆完全释放:43517K-2677K =40840

  • 所以老年代 = 43416 - 40840 = 2576K,两种方法算出来结果相等,都是老年代的大小

  • 总结第四次就是,新生代的对象达到阈值的一批进入老年代,新生代空间存活对象大大减少

  • 重点看第五次,第五次之前直接多加了3个大小为1M的数组,总计3M直接把可用ser区撑满了,所以JVM通过比较当前占用较大空间的年龄段值和原阈值大小,将阈值从新设置为两者中较小值,所以阈值被改为1 :new threshold 1 (max 3).在通过参数40840K->3122K(46080K), 0.0010328 secs] 43416K->5699K(125952K)可以看到,

  • gc完后老年代大小仍然为5699K-3122K = 2M,所以只对阈值进行修改,具体晋升还等下次gc
    *这次就体现出JVM会根据当前survivor区使用大小来进行动态调整阈值

*第6次因为上次阈值被改,导致大量对象晋升 老年代一下变为5655K-6K=5M,大量晋升后新生代空间管够,阈值又被回调new threshold 3 (max 3)
*
至此这个过程完毕,这个实例对日志的分析达到对阈值的功能做出诠释!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值