Java虚拟机-内存分配策略

时间:2022-02-21 09:54:16

Java技术体系中所提倡的自动内存管理策略最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存;

几条普遍的分配规则:
1、对象优先在Eden区分配
年轻代分为三个区:1个Eden区+2个Survivor区。
大部分对象在Eden区中生成(大对象可以直接被创建在年老代),还存活的对象将被复制到一个Survivor区,当这个Survivor区满时,此区的存活对象将被复制到剩下的一个Survivor区中,当这个Survivor区也满了的时候,从第一个Survivor复制过来的并且此时还存活的对象,将被复制到年老区。
需要注意的是,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden区复制过来的对象和从前一个Survivor区复制过来的对象,而复制到年老区的就只有从第一个Survivor区过去的对象。并且Survivor区总有一个是空的。
Minor GC(Young GC)对象被创建时,内存的分配首先发生在年轻代,大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉,这个GC机制被称为Minor GC或Young GC。
注意Minor GC并不代表年轻代内存不足,它事实上只表示在Eden区上的GC。
由于绝大部分对象都是短命的,甚至存活不到Survivor区中,所以Eden与Survivor的比例较大,HotSpot默认为8:1.

在Eden区,HotSpot虚拟机使用了两种技术来加快内存分配。分别是bump-the-pointer和TLAB(Thread- Local Allocation Buffers),这两种技术的做法分别是:由于Eden区是连续的,因此bump-the-pointer技术的核心就是跟踪最后创建的一个对象,在对象创建时,只需要检查最后一个对象后面是否有足够的内存即可,从而大大加快内存分配速度;而对于TLAB技术是对于多线程而言的,将Eden区分为若干段,每个线程使用独立的一段,避免相互影响。TLAB结合bump-the-pointer技术,将保证每个线程都使用Eden区的一段,并快速的分配内存;

 

2、大对象直接分配在老年代
所谓的大对象是指的需要大量连续内存空间的Java对象。比如:那种很长的字符串以及数组。
经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们;
年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命周期较长的对象。
年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数也比年轻代少。
当年老代内存不足时,将执行Major GC,也叫Full GC;
可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。
一般,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续;

 

在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。
使用-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配;这样的目的主要是为了避免在Eden区以及2个Survivor区之间发生大量的内存复制;
-XX:PretenureSizeThreshold参数只对Serial 和ParNew 收集器有效,Parallel Sacvenge收集器不认识此参数。
-XX:PretenureSizeThreshold参数的值不能像-Xms一样直接写3M要写成3145728;

 

3、长时间存活的对象分配在老年代
虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象在新生代,哪些对象应在老年代,所以,虚拟机给每个对象定义了一个对象年龄计数器
如果对象在Eden区出生并经历一次Minor GC后仍然存活,并且能被Survivor区所容纳,那么对象年龄计数器值为1;
对象在Survivor区中每熬过一次Minor GC,年龄就会增加1;
默认年龄是15,可以通过设置-XX:MaxTenuringThreshold来设置。

 

4、动态对象年龄判定
为了能更好的适应不同程序的内存状况,虚拟机并不是永远都是要求对象的年龄达到了MaxTenuringThreshold才能晋升老年代;
如果在Survivor空间内相同年龄所有对象大小总和大于 Survivor空间的一半,年龄等于或大于该年龄的对象可以直接进入老年代,而无需达到MaxTenuringThreshold设定的年龄;

 

5、空间分配担保
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总和。如果条件成立,那么Minor GC可以确保是安全的;
如果不成立,那么虚拟机会查看HandlerPromotionFailure设置值是否允许担保失败。
如果允许,会检查老年代最大可 用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试进行一次Minor GC;如果小于,则会进行一次Full GC