目前主流的垃圾收集器都會采用分代回收算法,因此需要將堆內存分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。
大多數情況下,對象在新生代中 eden 區分配。當 eden 區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC.下面我們來進行實際測試以下。
在測試之前我們先來看看?Minor GC 和 Full GC 有什么不同呢?
- 新生代 GC(Minor GC):指發生新生代的的垃圾收集動作,Minor GC 非常頻繁,回收速度一般也比較快。
- 老年代 GC(Major GC/Full GC):指發生在老年代的 GC,出現了 Major GC 經常會伴隨至少一次的 Minor GC(并非絕對),Major GC 的速度一般會比 Minor GC 的慢 10 倍以上。
測試:
public class GCTest {public static void main(String[] args) {byte[] allocation1, allocation2;allocation1 = new byte[30900*1024];//allocation2 = new byte[900*1024];}
}
通過以下方式運行:?
添加的參數:-XX:+PrintGCDetails
?
運行結果 (紅色字體描述有誤,應該是對應于 JDK1.7 的永久代):
從上圖我們可以看出 eden 區內存幾乎已經被分配完全(即使程序什么也不做,新生代也會使用 2000 多 k 內存)。假如我們再為 allocation2 分配內存會出現什么情況呢?
allocation2 = new byte[900*1024];
簡單解釋一下為什么會出現這種情況:?因為給 allocation2 分配內存的時候 eden 區內存幾乎已經被分配完了,我們剛剛講了當 Eden 區沒有足夠空間進行分配時,虛擬機將發起一次 Minor GC.GC 期間虛擬機又發現 allocation1 無法存入 Survivor 空間,所以只好通過?分配擔保機制?把新生代的對象提前轉移到老年代中去,老年代上的空間足夠存放 allocation1,所以不會出現 Full GC。執行 Minor GC 后,后面分配的對象如果能夠存在 eden 區的話,還是會在 eden 區分配內存。可以執行如下代碼驗證:
public class GCTest {public static void main(String[] args) {byte[] allocation1, allocation2,allocation3,allocation4,allocation5;allocation1 = new byte[32000*1024];allocation2 = new byte[1000*1024];allocation3 = new byte[1000*1024];allocation4 = new byte[1000*1024];allocation5 = new byte[1000*1024];}
}
大對象直接進入老年代
大對象就是需要大量連續內存空間的對象(比如:字符串、數組)。
為什么要這樣呢?
為了避免為大對象分配內存時由于分配擔保機制帶來的復制而降低效率。
private static final int _1MB = 1024 * 1024;/*** VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8* -XX:PretenureSizeThreshold=3145728*/
public static void testPretenureSizeThreshold() {byte[] allocation;allocation = new byte[4 * _1MB]; //直接分配在老年代中
}
執行后我們發現,新生代幾乎沒有被使用,老年代被使用了40%,因為超過一定數值的對象就會直接被分配到老年代。
長期存活的對象進入老年代
既然虛擬機采用了分代收集的思想來管理內存,那么內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。為了做到這一點,虛擬機給每個對象一個對象年齡(Age)計數器。
如果對象在 Eden 出生并經過第一次 Minor GC 后仍然能夠存活,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中,并將對象年齡設為 1.對象在 Survivor 中每熬過一次 MinorGC,年齡就增加 1 歲,當它的年齡增加到一定程度(默認為 15 歲),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數?-XX:MaxTenuringThreshold
?來設置。
private static final int _1MB = 1024 * 1024;/*** VM參數:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1* -XX:+PrintTenuringDistribution*/
@SuppressWarnings("unused")
public static void testTenuringThreshold() {byte[] allocation1, allocation2, allocation3;allocation1 = new byte[_1MB / 4]; // 什么時候進入老年代決定于XX:MaxTenuringThreshold設置allocation2 = new byte[4 * _1MB];allocation3 = new byte[4 * _1MB];allocation3 = null;allocation3 = new byte[4 * _1MB];
}
年齡判定
為了更好的適應不同程序的內存情況,虛擬機不是永遠要求對象年齡必須達到了某個值才能進入老年代,如果 Survivor 空間中相同年齡所有對象大小的總和大于 Survivor 空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無需達到要求的年齡。
分配擔保
?
?