Java虚拟机学习笔记(4)——内存分配与回收策略

时间:2022-12-23 18:42:23

          上一篇文章介绍了JVM的垃圾回收器,这一篇将介绍下Java都对象的内存分配和回收策略。

          首先,Java对象在哪里分配内存?在大多数情况下,Java对象都是在新生代得Eden区分配内存的,如果开启了本地线程分配缓冲(TLAB),则在TLAB中分配。注意:给对象分配内存时,如果Eden区没有足够得空间进行分配,虚拟机将发起一次Minor GC(新生代的GC)。

          现在看看下面一个例子:

/*
-Xms: 20M 设置堆最小值为 20M
-Xmx: 20M 设置堆最大值为 20M
-Xmn: 10M 将10M分配给新生代
-XX: SurvivorRatio=8 设置新生代中Eden区与一个Survivor区的空间比为8:1
*/
public class Test {
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2*1024*1024]; //在新生代Eden区分配2M内存
allocation2 = new byte[2*1024*1024]; //在新生代Eden区分配2M内存
allocation3 = new byte[2*1024*1024]; //在新生代Eden区分配2M内存
allocation4 = new byte[4*1024*1024]; //发现新生代Eden区和一个survivor区无法容纳4M,发生一次MInor GC
//由于空闲的Survivor区没法容纳6M的存活对象,空间分配担保,将6M对象移入老年代
//再将新的4M对象放入Eden区
}
}

 

大对象直接进入老年代

          大对象是指那种很长的字符串或是数组,大对象对虚拟机的内存分配来说是个坏消息,经常出现大对象容易导致内存还有不少空间时就提前触发了垃圾收集,以获取足够的连续空间来存放大对象。

          虚拟机提供了一一个:-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配。这样做的目的是避免在Eden区及两个Surivvor区之间发生大量的内存拷贝。


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

          既然虚拟机采用了分代收集的思想来管理内存,那内存回收时就必须能识别哪些对象应当放入新生代,哪些对象应该放入老年代。为了做到这一点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经历过第一次Minor GC后仍然存活,并且能够被Survivor容纳的话,该将被移动到Survivor空间中,并将该对象的年龄设为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁)时,就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold来设置。


动态对象年龄判断

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


空间分配担保

          在发生MInor GC之前,虚拟机会先检查老年代最大的可用连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么MInor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次MInor GC是有风险的;如果小于,或者HandlePromitionFailure设置不允许冒险,那这时要改为一次Full GC。

          JDK 6 update 24之后,HandlePromotionFailure参数不会再影响到到虚拟机的空间分配担保策略,担保规则为:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。