說明:是在Serial/Serial Old收集器下的內(nèi)存分配和回收策略
兩個(gè)概念
Minor GC:指發(fā)生在新生代的垃圾收集動作伐谈,因?yàn)镴ava對象大多都具備朝生夕死的特性铺董,所以Minor GC非常頻繁榔幸,一般回收速度也比較快奏路。
Full GC: 指發(fā)生在老年代的GC爱榕,出現(xiàn)了Major GC阵幸。Major GC的速度一般會比Minor GC 慢10倍以上
內(nèi)存的分配策略
1. 優(yōu)先在Eden上分配
大多數(shù)情況下花履,對象在新生代Eden上分配。當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí)挚赊,虛擬機(jī)將發(fā)起一起Minor GC诡壁。
我們可以看一個(gè)例子:
設(shè)置參數(shù):
-XX:+PrintGCDetails:收集器日志參數(shù)
-Xms20m:堆初始分配大小
-Xmx20m:堆最大可擴(kuò)展內(nèi)存空間,同-Xms值想同荠割,則意味著堆不可擴(kuò)展
-Xmn10m:新生代分配10M
剩下10M分配給老年代妹卿。
-XX:SurvivorRatio=8設(shè)定新生代中Eden區(qū)與Survivor區(qū)的空間占比是8:1,即Eden區(qū)域?yàn)?M蔑鹦。
private static final int _1MB = 1024 * 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]; // 出現(xiàn)一次Minor GC
}
運(yùn)行結(jié)果如下圖所示:
說明:
在為allocation4對象分配內(nèi)存空間時(shí)夺克,會發(fā)生一次Minor GC,GC的結(jié)果是新生代6651KB變?yōu)?48KB嚎朽,而總內(nèi)存占用量則幾乎沒有減少铺纽,因?yàn)閍llocation1、allocation2哟忍、allocation3三個(gè)對象都是存活的狡门,虛擬機(jī)幾乎沒有找到可回收的對象)
這次GC發(fā)生的原因是因?yàn)榻oallocation4分配內(nèi)存的時(shí)候,Eden已經(jīng)被占用了6MB锅很,剩下的空間不足以分配allocation4對象需要的4MB空間其馏,因此發(fā)生MinorGC。
但是粗蔚,GC期間發(fā)現(xiàn)3個(gè)2MB的對象全部無法放入Survivor空間尝偎,因?yàn)镾urvivor空間只有1MB大小,所以只能通過分配擔(dān)保機(jī)制鹏控,將Eden空間里分配的allocation1致扯、allocation2、allocation3對象轉(zhuǎn)移到老年代中当辐。
這次GC結(jié)束后抖僵,4MB的allocation4對象順利分配在Eden中,因此程序最后執(zhí)行完的結(jié)果是Eden占用4MB(被allocation4占用)缘揪,Survivor空閑耍群,老年代被占用6MB(被轉(zhuǎn)移來的allocation1义桂、allocation2、allocation3對象占用)
2. 大對象直接進(jìn)入老生代
所謂的大對象是指蹈垢,需要大量連續(xù)內(nèi)存空間的Java對象慷吊,最經(jīng)典的大對象就是那種很長的字符串以及數(shù)組。例如曹抬,下面例子中的byte[]數(shù)組就是典型的大對象溉瓶。
參數(shù):-XX:PretenureSizeThreshold(該設(shè)置只對Serial和ParNew收集器生效) 可以設(shè)置進(jìn)入老生代的大小限制,我們設(shè)置為3M谤民,則大于3M的大對象就直接進(jìn)入老年代
private static final int _1MB = 1024 * 1024;
/**
* VM 參數(shù):-verbose:gc -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails
* -XX:SurvivorRatio=8
* -XX:PretenureSizeThreshold=3145728
*/
public static void testPretenureSizeThreshold() {
byte[] allocation;
allocation = new byte[4 * _1MB]; // 直接分配在老年代中
}
3. 長期存活的對象進(jìn)入老年代
如果對象在Eden空間出生并經(jīng)過第一次MinorGC后仍然存活堰酿,并且能被Survivor空間容納的話,將被移動到Survivor空間中张足,并且對象年齡設(shè)為1.
對象在Survivor區(qū)中每“熬過”一次MinorGC触创,年齡增加1歲,當(dāng)它的年齡增加到一定程度为牍,默認(rèn)為15歲哼绑,就會被晉升到老年代中。晉升老年代的年齡閾值吵聪,可以通過參數(shù)-XX:MaxTenuringThreshold設(shè)置凌那。
4. 動態(tài)對象年齡判定
為了使內(nèi)存分配更加靈活,虛擬機(jī)并不要求對象年齡達(dá)到MaxTenuringThreshold才晉升老年代
如果Survivor區(qū)中相同年齡所有對象大小的總和大于Survivor區(qū)空間的一半吟逝,年齡大于或等于該年齡的對象在Minor GC時(shí)將復(fù)制至老年代
5. 空間分配擔(dān)保
新生代使用復(fù)制算法,當(dāng)Minor GC時(shí)如果存活對象過多赦肋,無法完全放入Survivor區(qū)块攒,就會向老年代借用內(nèi)存存放對象,以完成Minor GC佃乘。
在觸發(fā)Minor GC時(shí)囱井,虛擬機(jī)會先檢測之前GC時(shí)租借的老年代內(nèi)存的平均大小是否大于老年代的剩余內(nèi)存,如果大于趣避,則將Minor GC變?yōu)橐淮蜦ull GC庞呕,如果小于,則查看虛擬機(jī)是否允許擔(dān)保失敗程帕,如果允許擔(dān)保失敗住练,則只執(zhí)行一次Minor GC,否則也要將Minor GC變?yōu)橐淮蜦ull GC愁拭。
說白了讲逛,新生代放不下就會借用老年代的空間來進(jìn)行GC