关于Java虚拟机二三事(四)---内存分配与回收策略

前言
    上篇文章中,已经较为详细地介绍了虚拟机中垃圾收集器体系以及运行原理,本文将继续探讨给对象分配内存的具体策略。
    对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区,如果启动了本底线程分配缓存(TLAB),则将按线程优先在TLAB上分配。少数情况下,也可能直接分配至老年代中,分配的规则并非固定,其细节取决于当前所使用的哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置。

堆的逻辑分区
    新生代+老年代
关于Java虚拟机二三事(四)---内存分配与回收策略
   从上图可以直观地看出,JVM将堆的内存从逻辑上划分为新生代和老年代(老年代和新生代不一定大小相同),其中新生代又可以细分为Eden区和两个Survivor区(大小通常为8:1:1)。每个区的作用在内存分配策略中会详细介绍。

内存分配策略
    1.对象优先在Eden分配
关于Java虚拟机二三事(四)---内存分配与回收策略

   大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC对新生代中所有的对象进行垃圾回收这一动作。
关于Java虚拟机二三事(四)---内存分配与回收策略
关于Java虚拟机二三事(四)---内存分配与回收策略
  这里需要注意的一点是,我们可以发现,新生代里有两个空间大小一样的Survivor区,而新生代的可用空间大小为Eden区+一个Survivor区,而另一个Survivor区则用来存放Minor GC后仍旧存活的对象。
        而当Minor GC之后,Survivor区若无法容纳下存活的对象时,会通过分配担保机制,将对象转移到老年代。而转移的依据便是后文将会详细介绍的分配担保机制。

 2.大对象直接进入老年代
        所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。大对象对于虚拟机的内存分配而言,就是一个坏消息,因为从上节内容可知,新对象会优先分配在Eden区,如果Eden区内存空间不足,将进行一次Minor GC,大对象的出现极易引起频繁的Minor GC,因此,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾回收以获取足够的连续空间来“安置”这些大对象。
        虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配,这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制。

 
关于Java虚拟机二三事(四)---内存分配与回收策略
 3.长期存活的对象进入老年代
        既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能够识别哪些对象应该放在新生代,哪些对象应该放在老年代中。为了做到这一点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden区出生并经过第一次Minor GC后仍然存活,并且能够被Survivor容纳的话,将被移动到另一个备用Survivor中,并且对象年龄设为1,当它的年龄增加到一定程度时(默认为15岁),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数:-XX:MaxTenuringThreshold设置。
关于Java虚拟机二三事(四)---内存分配与回收策略
 4.动态对象年龄判断
        为了更好适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold才能晋升至老年代,如果在Survivor区中,相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象会直接进入老年代而无需等到MaxTenuringThreshold中要求的年龄。
        简单地概括一下就是如下两条公式:
                                  关于Java虚拟机二三事(四)---内存分配与回收策略
                                  关于Java虚拟机二三事(四)---内存分配与回收策略


即当Survivor区中,相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象会直接进入老年代而无需等到MaxTenuringThreshold中要求的年龄。
            
    5.空间分配担保机制
        在Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总和,如果这个条件成立,Minor GC可以确保是安全的,如果不成立,那么虚拟机会查看HandlePromotionFailure设置的值是否允许担保失败,如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果小于或者HandlePromotionFailure设置为不允许冒险,那么这是将进行一次Full GC。