内存的分配与内存的回收一样,都是为了减少GC时间而服务的。总体来说内存的分配是在堆上进行(但是也可能经过JTI编译后被拆分标量类型在栈上分配),优先在Eden区里分配。如果启动本地线程分配缓冲,按线程优先在TLAB上分配。
1.对象优先在Eden区分配
绝大多数对象的生存时间很短,所以将对象先分配在Eden区,等到Eden区满了之后进行Young GC,GC时Eden区存活的对象和From区的存活对象进入To区。如果To区放不下,就要放到老年代里。怎样判断老年代能不能放下呢?就是下文的空间分配担保。
优先在Eden区分配感觉主要是利用了新生代的复制算法,速度最快效率最高。新生代的GC速度比老年代的GC速度快10倍以上,所以在Eden区分配。
2.大对象直接进入老年代
大对象指需要大量连续内存空间的对象。具体多大可以手动设置-XX:PretenureSizeThreshold参数,大于这个值的直接分配在老年代。主要为了避免大对象在新生代之间进行大量的复制。
如果一个大对象进入老年代后没多久就死了,那必然很影响GC效率。所以最好不要创建大对象。G1收集器为了应对这种情况,专门开辟了一个Humongous区用来存放大对象。
3.长期存活的对象进入老年代
虚拟机每个对象头中都都有4个bit来存储分代年龄,如果在Eden区出生并经历一次YGC后仍然存活,而且被Survivor容纳的话,设置Age为1。并且每经历一次GC年龄就+1,当年龄超过一定界限之后将对象晋升为老年代。
参数可以通过-XX:MaxTenuringThreshold参数来设置,这个值最大为15,默认就是最大值。默认最大应该也是官方认为对象多在新生代呆着比较好,毕竟新生代GC速度很快。
4.动态对象年龄判定
并不是永远的要求对象的年龄大于某值才会晋升到老年代,如果Survivor空间中相同年龄所有对象大小的和大于Survivor空间的一般,年龄大于或者等于该年龄的对象就可直接进入老年代,无需满足年龄限制。
长期存活的对象和动态对象年龄判定都是要更好的利用新生代的GC,减少老年代的GC。如果某个Survivor有一半的空间都是某个年龄,首先Survivor的可用空间最多只剩下一半,其次如果这些对象一直得不到晋升,一直占着空间,那么当Survivor放不下Eden区存活的对象时,分代年龄就不起作用了,对象会直接进入到老年代,此时可能会让大量的短期对象进入老年代,引起FullGC 。特殊任务的执行或流量成分的变化,都会导致对象的生命周期发生波动,设置固定的阈值,无法适应这些动态变化,因此才会有动态对象年龄判定。
5.空间分配担保
YGC的时候,Survivor的To区放不下来自Eden和From中存活的对象,这是就应该把他们放进老年代。所以在进行YGC之前,要先判断老年区的可用空间,是不是比新生代的对象空间要大,因为防止年轻代所有对象都存活这种极端情况。如果老年代剩余空间大,那么可以进行YGC,大不了你所有的对象都进入老年代,老年代依然可以放的下。如果老年代剩余空间没那么大,就要看是否允许冒险。如果不允许冒险,那必选进行Full GC,如果允许冒险,就要判断老年代的可用空间和以前YGC进入老年代的平均值大小:就是老年代没有那么大,但是你以前YGC的时候进入老年代平均每次也就那么点,如果现在老年代比那平均值大,说明这次也有可能放的下,进行YGC,如果老年代比那个值小,这次放不下的概率大,不允许冒险,进行Full GC。如果允许冒险,最后确实放不下了,冒险失败启动FullGC,此时的FullGC是采用的备用收集器,一般都是单线程的速度很慢,所以比较耗时。