java对象内存分配过程_简单例子验证Java对象的分配过程

原标题:简单例子验证Java对象的分配过程

我们都知道Java对象分配在堆中,但是堆分新生代、老年代,新生代又分eden、from Survivor、to Survivor。今天通过简单的示例来验证下!

一、对象优先分配在Eden区

对象创建一般都优先放到eden区,jvm参数配置:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8。

示例运行结果如下图:

128964a720408afb0e98c2d9c260ed2e.png

很简单的程序,两个字节数组大小一共512k,加上jvm运行需要一些对象一共使用了新生代内存3243K,3242/2182约等于39%,survivor和老年代使用率0%。

当如果Eden区没有足够的空间时,虚拟机执行一次Minor GC,运行如下图:

900ffecbd1a872ec64b6d80af625c126.png

上一步我们知道eden总共9M用了3243K,然后我调用看一个方法产生了4M的数组,接着又准备分配一个3M的内存,很明显新生代内存肯定不够了,所以触发了GC。

GC日志简单学习:

1、图中第一个框GC表示新生代回收,如果是Full GC这里就会是Full GC;

2、第二个框表示触发回收的原因,这里的原因是分配失败;

3、第三个框表示新生代回收内存使用变化,回收前7175K(与3243k+4*1024k接近),回收后剩余992K

4、第四个框是总的堆内存回收前后变化,7175K减少到1120K,从后面堆内存信息可以看到,老年代有228K,992+228=1120。

看最终堆内存信息,from使用了1024*96%=992,刚好就是回收后的内存,说明上一次回收后对象都放到了这里,并且把他作为了from survivor。

eden使用了4468-992=3476,3476/8196约等于42%,其中包含新建的3M数组。

二、大对象直接进入老年代

这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。

这里要设置两个参数:1、-XX:PretenureSizeThreshold=3145728表示超过指定大小(这里是3M)就直接分配到老年代。2、-XX:+UseSerialGC表示使用Serial收集器,我按默认的收集器(PS收集器)不成功。

直接看代码运行如下图:

548e533a31b00d9f571a17684e1f2309.png

可以看到在老年代分配了4M内存,说明分配成功。

三、长期存活的对象进入老年代

虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,直到达到阀值对象进入老年区。

实例代码jvm参数配置:-verbose:gc、-Xms40M、-Xmx40M、-Xmn20M、-XX:+PrintGCDetails、-XX:SurvivorRatio=8、-XX:MaxTenuringThreshold=3、-XX:+PrintTenuringDistribution、-XX:+UseSerialGC

-XX:MaxTenuringThreshold=3参数表示对象的年龄超过3就会被放进老年代;

-XX:+PrintTenuringDistribution打印的是survivor里面的对象年龄信息;

代码如下图:

a1775a965d7827a508bd216d289f9aaf.png

每个打印的后面都会调用一个方法创建8M的数组,所以都会触发一次回收,在主方法里交替创建256k和523k的数组(不会被回收)。

运行结果如下图:

27620b9c010489ef9e3b824810e46db2.png

这里只截取了从第四次回收开始的信息,简单按顺序介绍下框中的数据。

1、survivor的一半,引用新生代是20M、-XX:SurvivorRatio=8,所以每一个survivor是2M,半个就是1M;

2、new threshold 3表示jvm计算出来的回收阀值,max 3就是-XX:MaxTenuringThreshold=3设置的最大晋升老年代年龄;

3、分别表示survivor各个年龄的对象大小,可以看到是512k和256k交替出现,等到下次回收的时候年龄3的进入了老年代,各个年龄段又交替变化了;

4、是各年龄段的累加,年龄2的值是年龄1加年龄2,年龄3是年龄1、2、3累计;

5、新生代被使用的总内存;

6、看最后一次回收survivor内存1311024/(2048*1024)刚好约等于62%,说明这里面全是那几个数组;

7、老年代使用的内存只有1642K,包括最开始建的三个数组和jvm本来的。

如果是一步一步的看堆内存的话就更能看到数组被一个一个的放到老年代了。

四、动态判断对象年龄

动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。

jvm参数与上一步一样,不用修改。

代码如下:

d251f3c6bd4ac0041a0688be13abc7b4.png

触发了第一次回收后,新建多5个256K的数组,超过了Survivor的一般1024,所以下次触发就会把数组1~6全部进入老年代。

运行结果如下图:

4ae9db46618d1072fcd851647196146a.png

可以看到,第二次回收的时候“new threshold 1”表示下次回收的时候最大年龄是1,可以看到第三次回收survivor中就只有年龄1的256K。上一步的两个已经进入老年代了。

五、老年代空间分配担保

每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC。

先利用-XX:PretenureSizeThreshold=9437184参数保存两个9M数组数组把老年代基本填满,然后新生代多次触发回收;

代码如下图:

248847a5a5e11ba8113388e7998757ee.png

运行结果如下图:

6d7274d3f011f629e35f6a3aaae67cbe.png

最后两次survivor中对象大小达到他的一半,所以会进入老年代,回收几次后老年代内存不足,触发Full GC.

六、总结

对象在堆中的流转就大概分为以上5种,已经用例子一一证明了,现在来总结下。分两种情况:

1、正常情况:进入eden->to survivor(下一次的from survivor)->to survivor->.......年龄上限->老年代,每个->代表一次GC。

2、特殊情况:a、大对象直接进入老年代;b、survivor中相同年龄的对象占的内存达到survivor的一半(一个survivor的一半),那么这个年龄以及年龄比它大的全部直接晋升老年代;

对象在堆中的流转情况比较简单,不过可以通过上面的例子并且去阅读GC日志能够理解更加的深刻。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

责任编辑:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值