在虚拟机中,我们知道对象的内存是分配在堆中的。但是堆又可以划分为更小的区域以便垃圾回收,那么,对象到底是怎么在分配在堆中的呢?
一:堆中优先分配Eden
大多数情况下,对象都在新生代的Eden区中分配内存。而因为大部分的对象都是“朝生夕死”的,所以新生代又会频繁进行垃圾回收。
1.1 案例
public class Test02 { public static void main(String[] args) { byte [] byte01=new byte[2 * 1024 * 1024 ]; byte [] byte02=new byte[2 * 1024 * 1024 ]; byte [] byte03=new byte[2 * 1024 * 1024 ]; byte [] byte04=new byte[4 * 1024 * 1024 ]; System.gc(); } }
- 结果
- 首先,-Xms、-Xmx分配用来设置进程堆内存的最小大小和最大大小。
- -Xmn用来设置堆内新生代的大小。通过这个值我们也可以得到老生代的大小:-Xmx减去-Xmn
- -XX:SurvivorRatio=8 表示
- 结果解析:正常来说,对象创建首先会分配到堆内存的eden区域,但是byte01~byte03对象共占6M内存,而byte04对象大小为4M,如果eden区域大小只有8M,故启动内存担保把byte01~byte03对象移到担保的内存中,也就是结果图中的
图中的内存大小就是6M,而此时byte04对象直接放到eden区域中。
二:大对象直接进入老年代
需要大量连续空间的对象,如:长字符串、数组等,会直接在老年代分配内存。这是因为,这样可以避免在新生代区频繁的GC时发生大量的内存赋值(新生代的GC是采用复制算法的)。
三:长期存活的对象“晋入”老年代
新生代中经历了多次GC仍然存活的对象,当年龄达到一定程度(默认15)时就会晋升到老年代。
为了更好地适应内存情况,虚拟机不是要求对象必须到达阀值才可晋升老年代的,而是采用动态年龄判定的方法:如果Servivor空间中相同年龄的对象大小大于Servivor空间的一般时,由于下一次的MinorGC时,这些对象如果仍然存活的话,复制到ToServivor空间时就放不下了。所以,在本次GC时就可以把这些对象以及年龄大于等于这些对象的直接进入老年代。
在MinorGC时,如果Eden和FromServivor中存活的对象在复制到ToServivor时放不下了,也会直接分配到老年代。
四:空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。