内存分配和回收策略
一、背景
Java技术中所说的自动内存管理最终归结为自动化地解决给对象分配内存和回收分配给对象的内存两个问题,这里阐述的就是给对象分配内存。
二、内存分配
1、Minior GC
Minior GC指新生代GC:指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生熄灭的特性,所以Minor GC非常平凡,一般回收速度也比较快。
2、Major GC
Major GC又是Full GC,指发生在老年代的GC,出现了Major GC,经常会伴随至少一次Minior GC,Major GC的速度一般都会比Minor GC慢10倍。
3、对象优先分配在Eden
大多数情况下,对象在新生代的Eden区中分配,当Eden区中没有足够的空间进行分配时,虚拟机将发生一次Minir GC。
4、大对象直接进入老年代
大对象指,指需要大量连续内存空间的Java对象,最典型的就是很长的字符串及数组。虚拟机提供 -XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代中分配,这样做的目的是为了米边Eden区及两个Survivor区之间发生大量的内存拷贝。
注意:PretenureSizeThreshold只对Serial和ParNew收集器有效。
5、长期存活的对象将进入老年代
虚拟机采用了分代收集的思想来管理内存,虚拟机给每个对象定义了一个对象年龄(Age)的计数器,这个计数器存在Java对象的对象头中。
如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor区中,这是Age设为1,;对象在Survivor区中每熬过一次Minior GC,Age就增加1,当它的年龄增加到一定程度(默认为15)时,就会被晋升到老年代中。这个年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。
6、动态对象年龄的判定
如果在Surivior空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接进入老年代。
7、空间分配担保
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlerPromotionFailure设置是否允许担保失败,如果允许,那只会进行Minor GC,如果不允许,则也要改为一次Full GC。
新生代使用复制收集算法,单位了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象Minor GC后仍然存活的情况时,就需要老年代进行分代担保,让Survivor无法容纳的对象直接进入老年代。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值来作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。
三、堆的划分及回收过程
Eden区最大,对外提供堆内存;当Eden区快要满了,则进行Minor GC,把存活对象放入Survivor A区,清空Eden区;
Eden区被清空后,继续对外提供堆内存;
当Eden区再次被填满,此时对Eden区和Survivor A区同时进行Minor GC,把存活的对象放入Survivor B区,同时清空Eden和Survivor A区;
Eden区继续对外提供堆内存,并重复上述过程,即在Eden区填满后,把Eden区和某个Survivor区中存活的对象,放入另外一个Survivor区中;
当某个Survivor区被填满,且仍有对象未被复制完毕时或者某些对象在反复Survivor 15次左右时,或者某些相同年龄对象的个数大于某个Survivor的区大小的一半时,则把这部分对象放入到Old区;
当Old区也被填满时,进行Major GC,对Old进行垃圾回收。