jvm对象从新生代到老年代_JVM(21)自己模拟老年代的场景(上)

喜欢文章的可以关注下公众号!谢谢支持!!

bb427ecfa2d2039611915d4e1a59d940.png

    上一篇文章我们已经初步分析了一次Young GC的日志,相信已经掌握了如何结合GC日志去分析一次Young GC执行的全过程。这篇文章我们接着之前的案例继续来做实验,动手体验一下对象是如何从新生代进入老年代的。

    动态年龄判定规则

    之前总结过对象进入老年代的4个常见的时机:

    [1]躲过15次gc,达到15岁高龄之后进入老年代。

    [2]动态年龄判定规则,如果Survivor区域内年龄1+年龄2+年龄3+年龄n的对象总和大于Survivor区的50%,此时年龄n以上的对象会进入老年代,不一定要达到15岁。

    [3]如果一次Young GC后存活对象太多无法放入Survivor区,此时直接计入老年代。

    [4]大对象直接进入老年代。

    首先我们先通过代码给大家模拟出来最常见的一种进入老年代的情况,如果Survivor区域内年龄1+年龄2+年龄3+年龄n的对象总和大于Survivor区的50%,此时年龄n以上的对象会进入老年代,也就是所谓的动态年龄判定规则。

    先看看我们这次示例程序的JVM参数:

“-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=10485760 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log”

    在这些参数里注意几点,新生代我们通过“-XX:NewSize”设置为10MB了。然后其中Eden区是8MB,每个Survivor区是1MB,Java堆总大小是20MB,老年代是10MB,大对象必须超过10MB才会直接进入老年代。但是我们通过“-XX:MaxTenuringThreshold=15”设置了,只要对象年龄达到15岁才会直接进入老年代。一切准备就绪,先看看我们当前的内存分配情况,如下图,然后接下来我们开始来看看我们的示例代码。

4c99ff8db7b8f41c8e37facaa0eba52d.png


    jvm运行过程,一定要一步一步调试代码,上面只是例子的部分代码,但是我们需要先运行部分代码,通过gc日志来分析这部分代码执行过后jvm中的对象分配情况。

    部分示例代码运行后产生的gc日志

    接着把上述示例代码以及我们给出的JVM参数配合起来运行,此时会看到如下的GC日志,接着我们就开始一步一步分析一下这部分代码运行后的gc日志。

0.297: [GC (Allocation Failure) 0.297: [ParNew: 7260K->715K(9216K), 0.0012641 secs] 7260K->715K(19456K), 0.0015046secs] [Times: user=0.00 sys=0.00, real=0.00 secs]Heappar new generation total 9216K, used 2845K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000) from space 1024K, 69% used [0x00000000ff500000, 0x00000000ff5b2e10, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)concurrent mark-sweep generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000,0x0000000100000000)Metaspace used 2782K, capacity 4486K, committed 4864K, reserved 1056768K class space used 300K, capacity 386K, committed 512K, reserved 1048576K

    部分代码的GC日志分析


    首先我们先看下述几行代码:

213ab163857dc37742487b60100a2dcc.png

    这里连续创建了3个2MB的数组,最后还把局部变量array1设置为了null,所以此时的内存如下图所示:

12c5d71302234f9f4907718be9835bea.png

    接着执行了这行代码:byte[] array2 = new byte[128 * 1024];。此时会在Eden区创建一个128KB的数组同时由array2变量来引用,如下图:

e55e6d8586cd700c8ae9df77d60a35ad.png

    然后会执行下面的代码:byte[] array3 = new byte[2 * 1024 * 1024];
此时希望在Eden区再次分配一个2MB的数组,可行吗?

    因为此时Eden区里已经有3个2MB的数组和1个128KB的数组,大小都超过6MB了,Eden总共才8MB,此时是不可能让你创建2MB的数组的。因此此时一定会触发一次Young GC,接着我们开始看GC日志。

 ParNew: 7260K->715K(9216K), 0.0012641 secs

    这行日志清晰表明了,在GC之前年轻代占用了7260KB的内存,这里大概就是6MB的3个数组 + 128KB的1个数组 + 几百MB的一些未知对象,如下图所示:

0f98cdb95b6edb179dc7906d27c2acbf.png

    接着看,7260K->715K(9216K),一次Young GC过后,剩余的存活对象大概是715KB,还记得上篇文章分析的GC日志吗?

    之前就说过大概年轻代刚开始会有512KB左右的未知对象,此时再加上我们自己的128KB的数组,大家想想,是不是差不多就是700KB?

    接着看GC日志如下:

par new generation total 9216K, used 2845K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000) from space 1024K, 69% used [0x00000000ff500000, 0x00000000ff5b2e10, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)concurrent mark-sweep generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000,0x0000000100000000)


    从上面的日志可以清晰看出,此时From Survivor区域被占据了69%的内存,大概就是700KB左右,这就是一次Young GC后存活下来的对象,他们都进入From Survivor区了。

    同时Eden区域内被占据了26%的空间,大概就是2MB左右,这就是byte[] array3 = new byte[2 * 1024 * 1024];,这行代码在gc过后分配在Eden区域内的数组。如下图所示:

    现在Survivor From区里的那700kb的对象,是几岁呢?

    答案是:1岁


    他熬过一次gc,年龄就会增长1岁。而且此时Survivor区域总大小是1MB,此时Survivor区域中的存活对象已经有700KB了,绝对超过了50%。

fd3050cbc78edaff943651799f81c91a.png

    我们把示例代码给完善一下,变成上述的样子,我们要触发出来第二次Young GC,然后看看Survivor区域内的动态年龄判定规则能否生效。先看下面几行代码:

3d5a6204116cde4cd4c503d7d32187a5.png

    代码运行过后,实际上会接着分配2个2MB的数组,然后再分配一个128KB的数组,最后是让array3变量指向null,如下图所示:

87bbfbd7a6707e4a8997c11378df1a38.png

    此时接着会运行下面的代码:byte[] array4 = new byte[2 * 1024 * 1024];这个时候会发现,Eden区如果要再次放一个2MB数组下去,是放不下的了,所以此时必然会触发一次Young GC。

    使用上述的JVM参数运行这段程序会看到如下的GC日志:

0.269: [GC (Allocation Failure) 0.269: [ParNew: 7260K->713K(9216K), 0.0013103 secs] 7260K->713K(19456K), 0.0015501secs] [Times: user=0.00 sys=0.00, real=0.00 secs]0.271: [GC (Allocation Failure) 0.271: [ParNew: 7017K->0K(9216K), 0.0036521 secs] 7017K->700K(19456K), 0.0037342 secs][Times: user=0.06 sys=0.00, real=0.00 secs]Heappar new generation total 9216K, used 2212K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 27% used [0x00000000fec00000, 0x00000000fee290e0, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)concurrent mark-sweep generation total 10240K, used 700K [0x00000000ff600000, 0x0000000100000000,0x0000000100000000)Metaspace used 2782K, capacity 4486K, committed 4864K, reserved 1056768K class space used 300K, capacity 386K, committed 512K, reserved 1048576K

    接下来我们来分析这些GC日志。

    分析最终版的GC日志

    首先第一次GC的日志如下:

0.269: [GC (Allocation Failure) 0.269: [ParNew: 7260K->713K(9216K), 0.0013103 secs] 7260K->713K(19456K), 0.0015501secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

    这个过程刚才我分析过了。接着第二次GC的日志如下:

0.271: [GC (Allocation Failure) 0.271: [ParNew: 7017K->0K(9216K), 0.0036521 secs] 7017K->700K(19456K), 0.0037342 secs][Times: user=0.06 sys=0.00, real=0.00 secs]

    第二次触发Yuong GC,就是我们上述代码执行的时候,此时发现ParNew: 7017K->0K(9216K).。这行日志表明,这次GC过后,年轻代直接就没有对象了,也就是说没有任何存活对象,觉得可能吗?

    还记得array2这个变量一直引用着一个128KB的数组,他绝对是存活对象,还有那500多KB的未知对象,此时都去哪里了呢?

    首先看看上面的图,在Eden区里有3个2MB的数组和1个128KB的数组,这绝对是会被回收掉的,如下图所示:

ec826776bcc9571b3f922bd6fd4fab32.png

    接着其实此时会发现Survivor区域中的对象都是存活的,而且总大小超过50%了,而且年龄都是1岁。

    此时根据动态年龄判定规则:年龄1+年龄2+年龄n的对象总大小超过了Survivor区域的50%,年龄n以上的对象进入老年代。

    当然这里的对象都是年龄1的,所以直接全部进入老年代了,如下图。

2727522c59fe22029d2933995253a20f.png

    看下面的日志可以确认这一点:

concurrent mark-sweep generation total 10240K, used 700K [0x00000000ff600000, 0x0000000100000000,0x0000000100000000)

    CMS管理的老年代,此时使用空间刚好是700KB,证明此时Survivor里的对象触发了动态年龄判定规则,虽然没有达到15岁,但是全部进入老年代了。包括我们自己的那个array2变量一直引用的128KB的数组。

    然后array4变量引用的那个2MB的数组,此时就会分配到Eden区域中,如下图所示。

6214b25027ba69a0ec1db9c1a3820522.png

    看下面的日志:

eden space 8192K, 27% used [0x00000000fec00000, 0x00000000fee290e0, 0x00000000ff400000)


这里就说明Eden区当前就是有一个2MB的数组。

    然后再看下面的日志:

from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)

    两个Survivor区域都是空的,因为之前存活的700KB的对象都进入老年代了,所以当然现在Survivor里都是空的了。如果大家对这块有更好的见解以及问题,或者本文有书写错误的地方,也可以进行评论交流。点点关注哦!!!谢谢支持!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值