jvm内存管理
在整个java技术体系中,自动内存管理简单说来就是解决两个问题:给对象分配内存和回收分给对象的内存。关于回收内存,上篇博客已经介绍了—>java垃圾收集。接下来说说关于给对象分配内存的问题。对象的内存分配,往大方向说就是在堆上分配,分配规则根据不同的垃圾收集器组合也不尽相同,下面记录了几条最普遍的分配规则。
1. 对象优先在Eden分配
多数情况下,对象在新生代Eden区中分配,当该区没有足够空间进行分配时,虚拟机会发起一次Minor GC。说到这,解释下Minor GC和Major GC:
Minor GC指发生在新生区的垃圾收集行为,Minor GC特点是非常频繁,回收速度较快。
Major GC 也叫Full GC,指发生在老年代的GC。特点和Minor GC相反,回收频率低,速度慢。
2. 大对象直接进入老年代
大对象是指需要大量连续内存空间的java对象,例如很长的字符串或数组。我们写程序的时候尽量避免写出“短命的大对象”,这会导致在还有内存的情况下虚拟机提前进行垃圾收集来腾出空间安置大对象。
虚拟机提供了一个参数-XX:PretenureSizeThreshold,大于该值的对象将直接分配到老年代,这样做避免了在Eden区和Survivor区之间发生大量内存复制(上篇已经提到新生代使用复制算法)。
3. 长期存活的对象进入老年代
关于老年代和年轻代,虚拟机为了实现分代收集的思想,给每个对象定义了一个年龄(Age)计数器。对象在Eden出生经过第一次Minor GC后存活并且被移动到了Survivor区,对象年龄即为1,以后每当该对象在Survivor区熬过一次Minor GC,年龄就会加一。当它的年龄增加到一定程度后,就会进入老年代。这个阈值可以通过参数-XX:MaxTenuringThreshold设置,默认为15。
4. 动态对象年龄判定
为了更好的适应不同程序内存特点,虚拟机并非永远要求对象年龄达到MaxTenuringThreshold才能晋升老年代。如果在Survivor空间中相同年龄的所有对象总和大于Survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代。
5. 空间分配担保机制
当一个对象要进入新生代时,Survivor区空间如果不够当前存活的对象全部复制的话,需要老年代进行分配担保。在Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代对象总空间,如果大于,那么Minor GC是安全的;如果不大于,虚拟机会查看HandlePromotionFailure设置是否允许担保失败,如果允许,继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则进行Minor GC(有一定风险),如果小于则进行一次Full GC(Major GC);如果HandlePromotionFailure设置不允许担保失败且老年代最大可用的连续空间小于新生代对象总空间,也要进行一次Full GC。关于上面所说的风险,这里说明一下:
因为新生代使用复制算法,为了提高内存利用率,每次只使用其中一个Survivor区作为轮换备份,当出现大量对象在Minor GC后仍然存活,一般是指大于10%的对象存活,极端情况下所有对象都存活。Survivor区无法容纳这么多对象,这时候老年代需要提供空间担保,前提是老年代有足够的空间进行担保。根据历次新晋老年代对象平均值是一种概率事件,自然存在“风险”。
本篇介绍了Java虚拟机自动内存分配的主要规则。参考《深入理解java虚拟机》