JVM(一)---- 總結(jié)與專題目錄
JVM(二)----Java運(yùn)行時(shí)數(shù)據(jù)區(qū)域
JVM(三)----垃圾收集算法及Safe Point介紹
JVM(四)----HotSpot的垃圾收集器與內(nèi)存分配回收策略
JVM(五)----虛擬機(jī)類加載機(jī)制
本文將介紹HotSpot虛擬機(jī)中的垃圾收集器。
概覽
這個(gè)虛擬機(jī)中包含的所有收集器如下圖:
Young generation是年輕代,Tenured generation是老年代。如果兩個(gè)收集器之間有連線,則說(shuō)明它們可以搭配使用。
到目前為止,還沒(méi)有什么最好的收集器,更加沒(méi)有萬(wàn)能收集器。所以我們選擇的知識(shí)對(duì)具體應(yīng)用最合適的收集器或者收集器搭配。
評(píng)價(jià)特定應(yīng)用下,一款收集器的好壞,主要有兩個(gè)指標(biāo):停頓時(shí)間和吞吐量。停頓時(shí)間是指進(jìn)行垃圾收集時(shí),用戶線程的暫停時(shí)間,也就是之前課程所說(shuō)的“Stop The World”,一般來(lái)說(shuō),用戶交互較為頻繁的B/S應(yīng)用更為重視停頓時(shí)間的長(zhǎng)短,停頓時(shí)間越短,用戶等待時(shí)間就越少,體驗(yàn)就越佳。
吞吐量是指用于執(zhí)行用戶線程的時(shí)間占總應(yīng)用時(shí)間的比率,對(duì)于無(wú)需和用戶進(jìn)行交互的純后臺(tái)應(yīng)用來(lái)說(shuō),停頓時(shí)間沒(méi)那么重要,更看重的是吞吐量的大小,吞吐量越大,說(shuō)明執(zhí)行用戶線程的時(shí)間更長(zhǎng),處理速度就越高。
Serial 收集器
Serial 收集器是最基本、歷史最悠久的收集器。這是一個(gè)單線程的收集器,有以下兩個(gè)特點(diǎn):
- 只使用一個(gè)CPU或一條收集線程去完成垃圾收集;
- 進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,也就是前面課程提到過(guò)的“Stop The World”;
這個(gè)單線程的收集器,是HotSpot運(yùn)行在Client模式下的默認(rèn)新生代收集器,主要是由于它有以下的優(yōu)勢(shì):
對(duì)于單個(gè)CPU的環(huán)境來(lái)說(shuō),單線程的Serial 收集器不需要進(jìn)行線程切換,減少了切換時(shí)的時(shí)間開(kāi)銷,因此在單CPU的環(huán)境下可以獲得最高的收集效率;對(duì)于大多數(shù)客戶端桌面應(yīng)用來(lái)說(shuō),分配給虛擬機(jī)的內(nèi)存一般不大,新生代的垃圾一般在幾十兆到一兩百兆之間,停頓時(shí)間完全可以控制在幾十毫秒到一百多毫秒以內(nèi),這點(diǎn)是可以接受的。因此,Serial 收集器對(duì)于運(yùn)行在客戶端、單CPU環(huán)境的虛擬機(jī)來(lái)說(shuō),是一個(gè)很好的選擇。
ParNew 收集器
ParNew 收集器其實(shí)就是Serial 收集器的多線程版本,除了使用多條線程進(jìn)行垃圾收集之外,其他都和Serial 收集器一致。
ParNew 收集器是虛擬機(jī)運(yùn)行在Server模式下的首選新生代收集器,除了因?yàn)樗嵌嗑€程收集器之外,還因?yàn)樗浅薙erial 收集器,唯一一個(gè)能和CMS收集器配合的收集器。
ParNew 收集器在單CPU的環(huán)境中絕對(duì)不會(huì)有比Serial 收集器更好的收集效率,甚至由于線程切換的開(kāi)銷,它在通過(guò)超線程技術(shù)實(shí)現(xiàn)的兩個(gè)CPU的環(huán)境中都不能百分百保證可以超越Serial 收集器。
當(dāng)然,隨著CPU數(shù)量的增加,ParNew 收集器的多線程優(yōu)勢(shì)會(huì)越發(fā)明顯,它默認(rèn)開(kāi)啟的收集線程數(shù)和CPU的數(shù)量相同,可以使用-XX:ParalletGCThreads參數(shù)來(lái)限制垃圾收集的線程數(shù)。
Parallel Scavenge 收集器
Parallel Scavenge 收集器,和ParNew 收集器一樣,是新生代收集器、同樣采用復(fù)制算法、同樣是多線程收集,那么,它有什么特別之處呢?
Parallel Scavenge 收集器最大的特點(diǎn)是它的關(guān)注點(diǎn)在于獲得一個(gè)可以控制的吞吐量。所謂吞吐量就是CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值。即吞吐量=運(yùn)行用戶代碼的時(shí)間/(運(yùn)行用戶代碼的時(shí)間+垃圾收集的時(shí)間)。
它提供了兩個(gè)參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMills和直接設(shè)置吞吐量大小的-XX:GCTimeRatio。
MaxGCPauseMills的值越小,系統(tǒng)就會(huì)將新生代的大小調(diào)的越小,以加快垃圾收集的速度,但是這樣也會(huì)增加了垃圾收集的頻率,自然吞吐量就下去了。
GCTimeRatio是垃圾收集時(shí)間占總時(shí)間的比率,相當(dāng)于吞吐量的倒數(shù)。可以精確地控制吞吐量。
實(shí)際使用時(shí),經(jīng)常會(huì)使用這兩個(gè)參數(shù)中的一個(gè),再配合另一個(gè)參數(shù),-XX:UseAdaptiveSizePolicy. UseAdaptiveSizePolicy是一個(gè)開(kāi)關(guān)參數(shù),這個(gè)參數(shù)打開(kāi)之后,就不需要手工去指定新生代的大小(-Xmn)、Eden和Survivor的比例(-XX:SurvivorRatio),虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況,動(dòng)態(tài)調(diào)整這些參數(shù),以實(shí)現(xiàn)你所設(shè)置的最大垃圾收集時(shí)間或者最大吞吐量的目標(biāo)。這種有點(diǎn)人工智能、又有點(diǎn)傻瓜式的調(diào)節(jié)方式,叫做GC的自適應(yīng)調(diào)節(jié)策略(GC Ergonomics)。
Serial Old收集器
Serial Old 收集器是Serial 收集器的老年代版本,同樣是單線程收集器,同樣是為了給Client模式的虛擬機(jī)使用,使用“標(biāo)記-整理”算法。
當(dāng)運(yùn)行在Server模式下時(shí),它主要有兩大用途:
- 在JDK 1.5以及之前的版本中,和Parallel Scavenge 收集器配合使用;
-
作為CMS收集器的后備方案,在發(fā)生Concurrent Mode Failure時(shí)使用,后面會(huì)詳細(xì)介紹;
Serial/Serial Old收集器運(yùn)行示意圖
Parallel Old收集器
Parallel Old 收集器是Parallel Scavenge 收集器的老年代版本,采用多線程收集和“標(biāo)記-整理”算法。
在Parallel Old出現(xiàn)之前,如果新生代選擇了Parallel Scavenge,老年代除了Serial Old就沒(méi)有別的選擇,而由于受到單線程的Serial Old在服務(wù)器端表現(xiàn)的拖累,使用Parallel Scavenge也未必可以獲得吞吐量最大化的效果。
Parallel Old 收集器的出現(xiàn)讓“吞吐量?jī)?yōu)先”收集器終于有了合適的應(yīng)用組合。
CMS收集器
CMS(Concurrent Mark Sweep)收集器的定位是獲取最短的"Stop The World"的時(shí)間,也就是最短停頓時(shí)間,在具有大量用戶交互使用的B/S應(yīng)用上,停頓時(shí)間越短,就越能給用戶帶來(lái)好的體驗(yàn)。那么CMS是如何做到最短停頓的呢?
并發(fā):用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能會(huì)交替執(zhí)行),用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)CPU上。
并行:多條垃圾收集線程并行工作,但此時(shí)用戶線程還是處于等待狀態(tài)。
答案是并發(fā)。CMS實(shí)現(xiàn)了垃圾收集線程和用戶線程的并發(fā)執(zhí)行,從名字上就可以看出,CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的,它的運(yùn)作過(guò)程有以下4個(gè)步驟:
1.初始標(biāo)記(CMS initial mak)
2.并發(fā)標(biāo)記(CMS concurrent mark)
3.重新標(biāo)記(CMS remark)
4.并發(fā)清除(CMS concurrent sweep)
其中,初始標(biāo)記和重新標(biāo)記是僅有的兩個(gè)需要“Stop The World”的階段。其他兩個(gè)階段都不需要。
初始標(biāo)記只是標(biāo)記以下GC Roots能直接關(guān)聯(lián)的對(duì)象,也就是下圖中和GC Roots有直接連線的object1,因此速度很快;
并發(fā)標(biāo)記就是進(jìn)行GC Roots Tracing,對(duì)所有與GC Roots不可達(dá)的對(duì)象進(jìn)行標(biāo)記。
重新標(biāo)記則是為了修正并發(fā)標(biāo)記期間,因用戶線程繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的時(shí)間一般比初始標(biāo)記的長(zhǎng),但遠(yuǎn)比并發(fā)標(biāo)記短;
并發(fā)清除,這個(gè)階段JVM將啟動(dòng)多條線程將所有標(biāo)記為unreachable的對(duì)象清除掉。
整個(gè)過(guò)程,耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除都可以與用戶線程同時(shí)工作,因此能夠?qū)崿F(xiàn)最短的停頓時(shí)間。
CMS絕對(duì)是一款十分優(yōu)秀的收集器,并發(fā)收集、低停頓,但是CMS還遠(yuǎn)不到完美,它有以下3個(gè)明顯缺陷:
1、CMS會(huì)搶占CPU資源。并發(fā)階段雖然不會(huì)導(dǎo)致用戶線程暫停,但卻需要CPU分出精力去執(zhí)行多條垃圾收集線程,從而使得用戶線程的執(zhí)行速度下降。
2、CMS無(wú)法處理浮動(dòng)垃圾(Floating Garbage),可能會(huì)出現(xiàn)“Concurrent Mode Failure”而導(dǎo)致另一次Full GC。并發(fā)清理的過(guò)程中,由于用戶線程還在執(zhí)行,因此就會(huì)繼續(xù)產(chǎn)生對(duì)象和垃圾,這些新的垃圾沒(méi)有被標(biāo)記,CMS只能在下一次收集中處理它們。這也導(dǎo)致了CMS不能在老年代幾乎完全被填滿了再去進(jìn)行收集,必須預(yù)留一部分空間提供給并發(fā)收集時(shí)程序運(yùn)作使用。在JDK1.5默認(rèn)設(shè)置下,老年代使用了68%(JDK1.6是92%)的空間后CMS的垃圾收集就會(huì)被激活,其實(shí)這是一個(gè)比較保守的設(shè)置,只要應(yīng)用中老年代增長(zhǎng)不是很快,可以適當(dāng)?shù)卣{(diào)高參數(shù)-XX:CMSInitialingOccupancyFraction來(lái)提高觸發(fā)百分比,降低回收的頻率來(lái)獲得更好的性能。如果CMS在收集期間,內(nèi)存無(wú)法滿足程序的需要,就會(huì)出現(xiàn)“Concurrent Mode Failure”,這時(shí)JVM將啟動(dòng)Plan B,也就是臨時(shí)調(diào)用單線程的Serial Old收集器來(lái)重新進(jìn)行老年代的垃圾收集,這樣的話,CMS原本降低停頓時(shí)間的目的不僅沒(méi)完成,和直接使用Serial Old收集器相比,還增加了前面幾個(gè)階段的停頓時(shí)間。
3、CMS的“標(biāo)記-清除”算法,會(huì)導(dǎo)致大量空間碎片的產(chǎn)生(為什么?)。碎片的積累會(huì)給分配大對(duì)象帶來(lái)麻煩,往往會(huì)出現(xiàn)明明老年代還有很多空間剩余,但是卻無(wú)法找到連續(xù)的空間分配對(duì)象的情況,這時(shí)候就不得不觸發(fā)一次Full GC。為了解決這個(gè)問(wèn)題。CMS提供了一個(gè)-XX:+UseCMSCompactAtFullCollection的開(kāi)關(guān)參數(shù)(默認(rèn)是開(kāi)啟的),用于在CMS收集器進(jìn)行Full GC時(shí)對(duì)內(nèi)存碎片進(jìn)行合并整理,整理的過(guò)程是需要暫停用戶線程的,這樣碎片雖然沒(méi)有了,但停頓時(shí)間又變長(zhǎng)了。CMS的設(shè)計(jì)初衷可是降低停頓,于是又提供了一個(gè)參數(shù)-XX:CMSFullGCsBeforeCompaction,用于設(shè)置執(zhí)行多少次不壓縮碎片的Full GC后,跟著來(lái)一次帶壓縮的Full GC(默認(rèn)值為0,即每次都會(huì))。
G1收集器
這個(gè)部分要終點(diǎn)關(guān)注!!!
G1(Garbage First)收集器是當(dāng)代收集器技術(shù)發(fā)展的最前沿成果之一,是一款面向服務(wù)端的收集器,HotSpot團(tuán)隊(duì)甚至希望G1收集器在未來(lái)可以替換掉CMS收集器。
G1收集器具有以下特點(diǎn):
并行和并發(fā):這一點(diǎn)和CMS是類似的,可以充分利用CPU的資源,來(lái)縮短“Stop The World”的時(shí)間,提高收集效率。
不產(chǎn)生空間碎片:和CMS的“標(biāo)記-清除”算法不同,G1從整體上看是采用“標(biāo)記-整理”,從局部又像是“復(fù)制”,但無(wú)論如何,它都不會(huì)像CMS一樣產(chǎn)生大量碎片而導(dǎo)致分配大對(duì)象失敗的情形。
可預(yù)測(cè)的停頓:這又是G1相對(duì)于CMS的一大優(yōu)勢(shì),CMS和G1都追求最低停頓時(shí)間,但是G1可以建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不超過(guò)N毫秒。
G1 總覽
首先是內(nèi)存劃分上,之前介紹的分代收集器將整個(gè)堆分為年輕代、老年代和永久代,每個(gè)代的空間是確定的。
而 G1 將整個(gè)堆劃分為一個(gè)個(gè)大小相等的小塊(每一塊稱為一個(gè) region),每一塊的內(nèi)存是連續(xù)的。和分代算法一樣,G1 中每個(gè)塊也會(huì)充當(dāng) Eden、Survivor、Old 三種角色,但是它們不是固定的,這使得內(nèi)存使用更加地靈活。
執(zhí)行垃圾收集時(shí),和 CMS 一樣,G1 收集線程在標(biāo)記階段和應(yīng)用程序線程并發(fā)執(zhí)行,標(biāo)記結(jié)束后,G1 也就知道哪些區(qū)塊基本上是垃圾,存活對(duì)象極少,G1 會(huì)先從這些區(qū)塊下手,因?yàn)閺倪@些區(qū)塊能很快釋放得到很大的可用空間,這也是為什么 G1 被取名為 Garbage-First 的原因。
在 G1 中,目標(biāo)停頓時(shí)間非常非常重要,用 -XX:MaxGCPauseMillis=200 指定期望的停頓時(shí)間。
G1 使用了停頓預(yù)測(cè)模型來(lái)滿足用戶指定的停頓時(shí)間目標(biāo),并基于目標(biāo)來(lái)選擇進(jìn)行垃圾回收的區(qū)塊數(shù)量。G1 采用增量回收的方式,每次回收一些區(qū)塊,而不是整堆回收。
我們要知道 G1 不是一個(gè)實(shí)時(shí)收集器,它會(huì)盡力滿足我們的停頓時(shí)間要求,但也不是絕對(duì)的,它基于之前垃圾收集的數(shù)據(jù)統(tǒng)計(jì),估計(jì)出在用戶指定的停頓時(shí)間內(nèi)能收集多少個(gè)區(qū)塊。
注意:G1 有和應(yīng)用程序一起運(yùn)行的并發(fā)階段,也有 stop-the-world 的并行階段。但是,F(xiàn)ull GC 的時(shí)候還是單線程運(yùn)行的,所以我們應(yīng)該盡量避免發(fā)生 Full GC,后面我們也會(huì)介紹什么時(shí)候會(huì)觸發(fā) Full GC。
G1 比 ParallelOld 和 CMS 會(huì)需要更多的內(nèi)存消耗,那是因?yàn)橛胁糠謨?nèi)存消耗于簿記(accounting)上,如以下兩個(gè)數(shù)據(jù)結(jié)構(gòu):
- Remembered Sets:每個(gè)區(qū)塊都有一個(gè) RSet,用于記錄進(jìn)入該區(qū)塊的對(duì)象引用(如區(qū)塊 A 中的對(duì)象引用了區(qū)塊 B,區(qū)塊 B 的 Rset 需要記錄這個(gè)信息),它用于實(shí)現(xiàn)收集過(guò)程的并行化以及使得區(qū)塊能進(jìn)行獨(dú)立收集。總體上 Remembered Sets 消耗的內(nèi)存小于 5%。
- Collection Sets:將要被回收的區(qū)塊集合。GC 時(shí),在這些區(qū)塊中的對(duì)象會(huì)被復(fù)制到其他區(qū)塊中,總體上 Collection Sets 消耗的內(nèi)存小于 1%。
G1 工作模式
G1 收集器提供三種垃圾回收模式:young gc、mixed gc、full gc,在不同的條件下觸發(fā)。
young gc
發(fā)生在年輕代的GC算法,一般對(duì)象(除了巨型對(duì)象)都是在eden region中分配內(nèi)存,當(dāng)所有eden region被耗盡無(wú)法申請(qǐng)內(nèi)存時(shí),就會(huì)觸發(fā)一次young gc,這種觸發(fā)機(jī)制和之前的young gc差不多,執(zhí)行完一次young gc,活躍對(duì)象會(huì)被拷貝到survivor region或者晉升到old region中,空閑的region會(huì)被放入空閑列表中,等待下次被使用。
mixed gc
當(dāng)越來(lái)越多的對(duì)象晉升到老年代old region時(shí),為了避免堆內(nèi)存被耗盡,虛擬機(jī)會(huì)觸發(fā)一個(gè)混合的垃圾收集器,即mixed gc,該算法并不是一個(gè)old gc,除了回收整個(gè)young region,還會(huì)回收一部分的old region,這里需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進(jìn)行收集,從而可以對(duì)垃圾回收的耗時(shí)時(shí)間進(jìn)行控制。
那么mixed gc什么時(shí)候被觸發(fā)?
先回顧一下cms的觸發(fā)機(jī)制,如果添加了以下參數(shù):
-XX:CMSInitiatingOccupancyFraction=80
-XX:+UseCMSInitiatingOccupancyOnly
當(dāng)老年代的使用率達(dá)到80%時(shí),就會(huì)觸發(fā)一次cms gc。相對(duì)的,mixed gc中也有一個(gè)閾值參數(shù) -XX:InitiatingHeapOccupancyPercent,當(dāng)老年代大小占整個(gè)堆大小百分比達(dá)到該閾值時(shí),會(huì)觸發(fā)一次mixed gc.
mixed gc的執(zhí)行過(guò)程有點(diǎn)類似cms,主要分為以下幾個(gè)步驟:
- 1.initial mark: 初始標(biāo)記過(guò)程,整個(gè)過(guò)程STW,標(biāo)記了從GC Root可達(dá)的對(duì)象
- 2.concurrent marking: 并發(fā)標(biāo)記過(guò)程,整個(gè)過(guò)程gc collector線程與應(yīng)用線程可以并行執(zhí)行,標(biāo)記出GC Root可達(dá)對(duì)象衍生出去的存活對(duì)象,并收集各個(gè)Region的存活對(duì)象信息
- 3.remark: 最終標(biāo)記過(guò)程,整個(gè)過(guò)程STW,標(biāo)記出那些在并發(fā)標(biāo)記過(guò)程中遺漏的,或者內(nèi)部引用發(fā)生變化的對(duì)象
- 4.clean up: 垃圾清除過(guò)程,如果發(fā)現(xiàn)一個(gè)Region中沒(méi)有存活對(duì)象,則把該Region加入到空閑列表中。該階段會(huì)STW。清點(diǎn)和重置標(biāo)記狀態(tài)。這個(gè)階段有點(diǎn)像mark-sweep中的sweep階段,這個(gè)階段并不會(huì)實(shí)際上去做垃圾的收集,只是去根據(jù)停頓模型來(lái)預(yù)測(cè)出CSet,等待evacuation階段來(lái)回收。
補(bǔ)充:拷貝存活對(duì)象階段
Evacuation階段是全暫停的。該階段把一部分Region里的活對(duì)象拷貝到另一部分Region中,從而實(shí)現(xiàn)垃圾的回收清理。Evacuation階段從第一階段選出來(lái)的Region中篩選出任意多個(gè)Region作為垃圾收集的目標(biāo),這些要收集的Region叫CSet,通過(guò)RSet實(shí)現(xiàn)。
篩選出CSet之后,G1將并行的將這些Region里的存活對(duì)象拷貝到其他Region中,這點(diǎn)類似于ParalledScavenge的拷貝過(guò)程,整個(gè)過(guò)程是完全暫停的。關(guān)于停頓時(shí)間的控制,就是通過(guò)選擇CSet的數(shù)量來(lái)達(dá)到控制時(shí)間長(zhǎng)短的目標(biāo)。
full gc
如果對(duì)象內(nèi)存分配速度過(guò)快,mixed gc來(lái)不及回收,導(dǎo)致老年代被填滿,就會(huì)觸發(fā)一次full gc,G1的full gc算法就是單線程執(zhí)行的serial old gc,會(huì)導(dǎo)致異常長(zhǎng)時(shí)間的暫停時(shí)間,需要進(jìn)行不斷的調(diào)優(yōu),盡可能的避免full gc。
介紹一下幾種會(huì)導(dǎo)致Full gc的情況:
1.concurrent mode failure:并發(fā)模式失敗,CMS 收集器也有同樣的概念。G1 并發(fā)標(biāo)記期間,如果在標(biāo)記結(jié)束前,老年代被填滿,G1 會(huì)放棄標(biāo)記。
這個(gè)時(shí)候說(shuō)明:
堆需要增加了,
或者需要調(diào)整并發(fā)周期,如增加并發(fā)標(biāo)記的線程數(shù)量,讓并發(fā)標(biāo)記盡快結(jié)束
或者就是更早地進(jìn)行并發(fā)周期,默認(rèn)是整堆內(nèi)存的 45% 被占用就開(kāi)始進(jìn)行并發(fā)周期。
2.晉升失敗:并發(fā)周期結(jié)束后,是混合垃圾回收周期,伴隨著年輕代垃圾收集,進(jìn)行清理老年代空間,如果這個(gè)時(shí)候清理的速度小于消耗的速度,導(dǎo)致老年代不夠用,那么會(huì)發(fā)生晉升失敗。
說(shuō)明混合垃圾回收需要更迅速完成垃圾收集,也就是說(shuō)在混合回收階段,每次年輕代的收集應(yīng)該處理更多的老年代已標(biāo)記區(qū)塊。
3.疏散失敗:年輕代垃圾收集的時(shí)候,如果 Survivor 和 Old 區(qū)沒(méi)有足夠的空間容納所有的存活對(duì)象。這種情況肯定是非常致命的,因?yàn)榛旧弦呀?jīng)沒(méi)有多少空間可以用了,這個(gè)時(shí)候會(huì)觸發(fā) Full GC 也是很合理的。
最簡(jiǎn)單的就是增加堆大小
4.大對(duì)象分配失敗,我們應(yīng)該盡可能地不創(chuàng)建大對(duì)象,尤其是大于一個(gè)區(qū)塊大小的那種對(duì)象。
內(nèi)存分配回收策略
1.對(duì)象優(yōu)先在Eden分配
大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配。 當(dāng)Eden區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí),JVM將進(jìn)行一次Minor GC。
此處的Minor GC指的是對(duì)新生代的GC。而Full GC/Major GC指的是對(duì)老年代的GC。
利用參數(shù)-XX:+PrintGCDetails -XX:+PrintGCTimeStamps可以查看相關(guān)的GC日志。
2.大對(duì)象直接進(jìn)入老年代
所謂的大對(duì)象是指,需要大量連續(xù)內(nèi)存空間的Java對(duì)象,最典型的大對(duì)象就是那種很長(zhǎng)的字符串以及數(shù)組。直接進(jìn)入老年代區(qū)域而不是分配到新生代。
JVM參數(shù)-XX:PretenureSizeThreshold的意思就是將體積大于這個(gè)設(shè)置值的對(duì)象直接在老年代分配。
這樣做是為了避免在Eden區(qū)及兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。
3.長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
既然虛擬機(jī)采用了分代收集的思想來(lái)管理內(nèi)存,那么內(nèi)存回收時(shí)就必須能識(shí)別哪些對(duì)象應(yīng)放在新生代,哪些對(duì)象應(yīng)放在老年代中。 為了做到這點(diǎn),虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器。 如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納的話,將被移動(dòng)到Survivor空間中,并且對(duì)象年齡設(shè)為1。 對(duì)象在Survivor區(qū)中每“熬過(guò)”一次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲),就將會(huì)被晉升到老年代中。
對(duì)象晉升老年代的年齡閾值,可以通過(guò)參數(shù)-XX:MaxTenuringThreshold設(shè)置。
4.對(duì)象年齡的動(dòng)態(tài)判定
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代。
如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到MaxTenuringThreshold中要求的年齡。
5.空間分配擔(dān)保
在發(fā)生Minor GC之前,虛擬機(jī)會(huì)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那么Minor GC可以確保是安全的。
如果不成立,則虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置值是否允許擔(dān)保失敗。
如果允許,那么會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試著進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的;
如果小于,或者HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那這時(shí)也要改為進(jìn)行一次Full GC。
參考資料
《深入理解Java虛擬機(jī)》周志明著
https://www.javadoop.com/post/g1#G1%20%E6%80%BB%E8%A7%88
http://www.lxweimin.com/p/0f1f5adffdc1
https://www.cnblogs.com/yunxitalk/p/8987318.html