首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

简单例子验证JAVA对象的分配过程

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

一、对象优先分配在Eden区

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

示例运行结果如下图:

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

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

上一步我们知道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收集器)不成功。

直接看代码运行如下图:

可以看到在老年代分配了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里面的对象年龄信息;

代码如下图:

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

运行结果如下图:

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

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参数与上一步一样,不用修改。

代码如下:

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

运行结果如下图:

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

五、老年代空间分配担保

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

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

代码如下图:

运行结果如下图:

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

六、总结

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

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

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

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

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

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200409A0TDHC00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券