一、java對象分配策略
java中所說的自動內(nèi)存管理最終可以歸結(jié)到兩個問題:
- 自動分配不存
- 自動回收內(nèi)存
對象的內(nèi)存分配主要是在堆上進(jìn)行,堆根據(jù)對象不同的存活周期分為不同的區(qū)域,新生對象一般分在了Eden區(qū)域,如果啟動了線程分配緩沖,則優(yōu)先會分配到TLAB上。有少數(shù)情況新生對象會直接分配到老年代區(qū)域。實(shí)際情況要根據(jù)虛擬機(jī)模式和收集器組合來確定。
以下結(jié)論是Client模式下配合Serial和Serial Old收集器。
1、對象優(yōu)先分配在Eden區(qū)域
在多數(shù)情況下,對象優(yōu)先分配在Eden區(qū)域,當(dāng)Eden區(qū)域的沒有足夠的內(nèi)存分配時,虛擬機(jī)將發(fā)生一次minor GC。
以下代碼測試新生對象的分配機(jī)制。設(shè)置堆大小為20M,不可擴(kuò)展,新生代大小為10M,老年代10M,Eden和from survivor和to survivor的比例為8:1:1。
public class TestAllocation {
private static final int _1MB = 1024;
//VM參數(shù) -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();
}
}
在進(jìn)行allocation1 ,allocation12, allocation13時對象正常分配到Eden區(qū),在分配第四個對象的時候發(fā)現(xiàn)Eden的空間不夠用,觸發(fā)一次Minor GC,minor GC過后對象1,2,3正常情況下應(yīng)該被復(fù)制算法復(fù)制到另一塊survivor空間,但是由于survivor空間不夠所以通過分配擔(dān)保將123轉(zhuǎn)移到了老年代空間中,此時Eden和兩塊survivor空閑,老年代空間占用6MB。最后第四個對象成功分配到Eden空間。
(在java8中運(yùn)行上述代碼,沒有出現(xiàn)相同的結(jié)果。。。。。)
2、大對象直接進(jìn)入老年代
大對象的存儲需要大量的連續(xù)的內(nèi)存空間,這對虛擬機(jī)的內(nèi)存管理來說不是一個好消息。虛擬機(jī)設(shè)置了-XX:PretenureSizeThreshold做標(biāo)志,當(dāng)新生對象大小超過了-XX:PretenureSizeThreshold設(shè)置的大小則獨(dú)享直接分配在老年代中。這樣做的好處是避免了在新生代中大量的復(fù)制(新生代采用的是復(fù)制算法)。壞處是增加了老年代的垃圾收集任務(wù)。且老年代的垃圾收集速度要比新生代耗時長很多。
3、長期存活的對象進(jìn)入老年代
虛擬機(jī)為每一個對象定義了一個對象年齡計(jì)數(shù)器,如果在出生在Eden經(jīng)過一個Minor GC后存活且被survivor容納,則age設(shè)為1。對象在survivor每熬過一次Minor GC則age加1,當(dāng)age增加到15時晉升到老年代中。晉升age可以調(diào)節(jié),參數(shù)為-XX:MaxTenuringThreshold。
虛擬機(jī)并不是永遠(yuǎn)要求對象的年齡達(dá)到-XX:MaxTenuringThreshold之后才能晉升到老年代。如果在Survivor空間的相同年齡所有對象大小的總和超過了Survivor空間的一半,則虛擬機(jī)就允許大于或等于該年齡的對象直接進(jìn)入老年代。
二、 其他概念總結(jié)
1、GC分類
GC按照發(fā)生位置分為Minor GC和Major GC:
- Minor GC:發(fā)生在新生代。由于新生代對象的特點(diǎn),Minor GC發(fā)生較頻繁,速度也很快。
- Major GC/Full GC:發(fā)生在老年代。Major GC一般會伴隨多次的Minor GC,速度也較Minor GC慢出一個數(shù)量級。
2、空間分配擔(dān)保
在新生代中使用的收集算法是復(fù)制算法,一般將新生代分成三個區(qū)域,Eden區(qū),from Survivor和to Survivor,這三個區(qū)域的比大小例是8:1:1。一半新生對象沒有特殊設(shè)置的話會直接出生在Eden區(qū)域,如果Eden區(qū)域空間不夠則會發(fā)生一次Minor GC。如果在Minor GC時Survivor空間不夠這就需要分配擔(dān)保。
分配擔(dān)保就是Minor GC過程中Survivor區(qū)域不足以容納Eden區(qū)的存活對象從而向老年代借一塊區(qū)域用于存放存活對象。
老年代在執(zhí)行分配擔(dān)保時會有一些執(zhí)行機(jī)制。
1、在Minor GC前虛擬機(jī)會檢查老年代的最大可用連續(xù)空間是否大于新生代所有對象總空間,如果大于,則分配擔(dān)保安全。
2、如果老年代最大連續(xù)總空間小于新生代所有對象之和。虛擬機(jī)檢查HandlePromotionFailure設(shè)置是否允許分配擔(dān)保失敗,如果允許。繼續(xù)檢查老年代最大連續(xù)空間是否大于歷次晉升到老年代對象的平均大小。如果大于,虛擬機(jī)將允許發(fā)生Minor GC,如果小于,HandlePromotionFailure設(shè)置為不允許,此時需要進(jìn)行一次Full GC。
(JDK 6 Update 24之后的規(guī)則變?yōu)橹灰夏甏倪B續(xù)空間大于新生代對象總大小或者歷次晉升的平均大小就會進(jìn)行Minor GC,否則將進(jìn)行Full GC。)