JVM内存分配策略
介绍
就是java对象在堆上分配空间的策略。
具体的分配策略取决于使用那种垃圾回收器的组合,还有虚拟机中参数的设置。这里的一般是分代收集算法下的情况。
内存结构
这里的内存结构说的是虚拟机对jvm规范中堆区域的实现,不同的虚拟机可能稍有不同。
根据分代回收算法,将堆分为新生代、老年代。新生代默认占堆空间的1/3,老年代默认占堆空间的2/3。
新生代使用复制算法,有3个区,Eden、To Survivor、From Survivor,它们的默认占比是8:1:1,它的执行流程如下:
设置Survivore区可以防止频繁触发FULL GC。如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代,这样会使老年代很快被填满,导致老年代触发FULL GC,由于老年代的内存空间远大于新生代,所以进行一次Full GC消耗的时间比Minor GC长得多。
两个Survivor防止产生内存空间碎片。如果只有Survivor1,那么每一次当Eden区满时,触发Minor GC并把对象移入Survivor1中,如此循环对导致Survivor1中产生大量的空间碎片;所以需要有Survivor2,当Eden再一次满时,触发Minor GC,虚拟机会把 Eden中和Survivor1中的存活对象通过复制算法移入Survivor2中,这样Survivor2就不会产生内存碎片,同时Eden和Survivor1会清理内存,保证下一次Minor GC触发时的操作。
- 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To Survivor区”,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中。
- 清空Eden和From Survivor分区。
- 这时From Survivor和To Survivor分区会互换角色,分区交换,From Survivor变To Survivor,To Survivor变From Survivor。
- 每次在From Survivor到To Survivor移动时都存活的对象,年龄就+1,当年龄到达15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
策略
常见的内存分配策略为:
多数情况下,新对象都在新生代Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC,即发生在新生代的垃圾收集。
如果分配后还是没有足够的空间,就会启动分配担保机制在老年代分配空间。
大对象直接进入老年代
所谓大对象就是指需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组。可以通过虚拟机参数-XX:PretenureSizeThreshold
控制大对象的最小临界值。
新生代使用的是复制算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致Eden区和两个Survivor区之间发生大量的内存复制。
经常出现大对象,会导致在内存还有不少时就触发垃圾回收。
- Minor GC:指发生在新生代的GC,一般速度非常快。
- Full GC:是指发生在老年代的GC,一般出现这种必然会伴随Minor GC,通常会被Minor GC慢10倍。
长期存活的对象进入老年代
在分代收集算法中需要确定那些对象该进入老年代,那些不用进入。
虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden区出生,并且能够被Survivor容纳,将被移动到Survivor空间中,这时设置对象年龄为1。对象在Survivor区中每过一次Minor GC年龄就加 1,当年龄达到一定程度(默认 15)就会被晋升到老年代。可以通过参数-XX:MaxTenuringThreshold
设置进入老年代的年龄上限。
动态对象年龄判断
虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象就可以直接进入老年代。
空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,这这时也要改为进行一次Full GC(指发生在老年代的GC,会发生STW)。实际上,在 JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则就进行Full GC。