一、java对象分配策略
java中所说的自动内存管理最终可以归结到两个问题:
- 自动分配不存
- 自动回收内存
对象的内存分配主要是在堆上进行,堆根据对象不同的存活周期分为不同的区域,新生对象一般分在了Eden区域,如果启动了线程分配缓冲,则优先会分配到TLAB上。有少数情况新生对象会直接分配到老年代区域。实际情况要根据虚拟机模式和收集器组合来确定。
以下结论是Client模式下配合Serial和Serial Old收集器。
1、对象优先分配在Eden区域
在多数情况下,对象优先分配在Eden区域,当Eden区域的没有足够的内存分配时,虚拟机将发生一次minor GC。
以下代码测试新生对象的分配机制。设置堆大小为20M,不可扩展,新生代大小为10M,老年代10M,Eden和from survivor和to survivor的比例为8:1:1。
public class TestAllocation {
private static final int _1MB = 1024;
//VM参数 -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
public static void testAllocation(){
byte[] allocation1, allocation2, allocation3, allocation4;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation4 = new byte[4 * _1MB];
}
public static void main(String[] args) {
testAllocation();
}
}
在进行allocation1 ,allocation12, allocation13时对象正常分配到Eden区,在分配第四个对象的时候发现Eden的空间不够用,触发一次Minor GC,minor GC过后对象1,2,3正常情况下应该被复制算法复制到另一块survivor空间,但是由于survivor空间不够所以通过分配担保将123转移到了老年代空间中,此时Eden和两块survivor空闲,老年代空间占用6MB。最后第四个对象成功分配到Eden空间。
(在java8中运行上述代码,没有出现相同的结果。。。。。)
2、大对象直接进入老年代
大对象的存储需要大量的连续的内存空间,这对虚拟机的内存管理来说不是一个好消息。虚拟机设置了-XX:PretenureSizeThreshold做标志,当新生对象大小超过了-XX:PretenureSizeThreshold设置的大小则独享直接分配在老年代中。这样做的好处是避免了在新生代中大量的复制(新生代采用的是复制算法)。坏处是增加了老年代的垃圾收集任务。且老年代的垃圾收集速度要比新生代耗时长很多。
3、长期存活的对象进入老年代
虚拟机为每一个对象定义了一个对象年龄计数器,如果在出生在Eden经过一个Minor GC后存活且被survivor容纳,则age设为1。对象在survivor每熬过一次Minor GC则age加1,当age增加到15时晋升到老年代中。晋升age可以调节,参数为-XX:MaxTenuringThreshold。
虚拟机并不是永远要求对象的年龄达到-XX:MaxTenuringThreshold之后才能晋升到老年代。如果在Survivor空间的相同年龄所有对象大小的总和超过了Survivor空间的一半,则虚拟机就允许大于或等于该年龄的对象直接进入老年代。
二、 其他概念总结
1、GC分类
GC按照发生位置分为Minor GC和Major GC:
- Minor GC:发生在新生代。由于新生代对象的特点,Minor GC发生较频繁,速度也很快。
- Major GC/Full GC:发生在老年代。Major GC一般会伴随多次的Minor GC,速度也较Minor GC慢出一个数量级。
2、空间分配担保
在新生代中使用的收集算法是复制算法,一般将新生代分成三个区域,Eden区,from Survivor和to Survivor,这三个区域的比大小例是8:1:1。一半新生对象没有特殊设置的话会直接出生在Eden区域,如果Eden区域空间不够则会发生一次Minor GC。如果在Minor GC时Survivor空间不够这就需要分配担保。
分配担保就是Minor GC过程中Survivor区域不足以容纳Eden区的存活对象从而向老年代借一块区域用于存放存活对象。
老年代在执行分配担保时会有一些执行机制。
1、在Minor GC前虚拟机会检查老年代的最大可用连续空间是否大于新生代所有对象总空间,如果大于,则分配担保安全。
2、如果老年代最大连续总空间小于新生代所有对象之和。虚拟机检查HandlePromotionFailure设置是否允许分配担保失败,如果允许。继续检查老年代最大连续空间是否大于历次晋升到老年代对象的平均大小。如果大于,虚拟机将允许发生Minor GC,如果小于,HandlePromotionFailure设置为不允许,此时需要进行一次Full GC。
(JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。)