垃圾收集器

垃圾收集器

Serial收集器

Serial收集器是最基本、發(fā)展歷史最悠久的收集器,曾經(jīng)(在JDK 1.3.1之前)是虛擬機新生代收集的唯一選擇。大家看名字就會知道,這個收集器是一個單線程的收集器,但它的“單線程”的意義并不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結(jié)束?!癝top The World”這個名字也許聽起來很酷,但這項工作實際上是由虛擬機在后臺自動發(fā)起和自動完成的,在用戶不可見的情況下把用戶正常工作的線程全部停掉,這對很多應(yīng)用來說都是難以接受的。讀者不妨試想一下,要是你的計算機每運行一個小時就會暫停響應(yīng)5分鐘,你會有什么樣的心情?Serial/Serial Old收集器的運行過程

Serial收集器

ParNew收集器

ParNew收集器其實就是Serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其余行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop TheWorld、對象分配規(guī)則、回收策略等都與Serial收集器完全一樣,在實現(xiàn)上,這兩種收集器也共用了相當(dāng)多的代碼。

●并行(Parallel):指多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態(tài)。

●并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時執(zhí)行(但不一定是并行的,可能會交替執(zhí)行),用戶程序在繼續(xù)運行,而垃圾收集程序運行于另一個CPU上。

ParNew收集器的工作過程

Parallel Scavenge收集器

Parallel Scavenge收集器是一個新生代收集器,它也是使用復(fù)制算法的收集器,又是并行的多線程收集器......看上去和ParNew都一樣,那它有什么特別之處呢?

Parallel Scavenge收集器的特點是它的關(guān)注點與其他收集器不同,CMS等收集器的關(guān)注點是盡可能地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。所謂吞吐量就是CPU用于運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同樣是一個單線程收集器,使用“標記-整理”算法。這個收集器的主要意義也是在于給Client模式下的虛擬機使用。如果在Server模式下,那么它主要還有兩大用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用 [1] ,另一種用途就是作為CMS收集器的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure時使用。這兩點都將在后面的內(nèi)容中詳細講解。


Serial Old收集器的工作過程

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。這個收集器是在JDK 1.6中才開始提供的,在此之前,新生代的Parallel Scavenge收集器一直處于比較尷尬的狀態(tài)。原因是,如果新生代選擇了ParallelScavenge收集器,老年代除了Serial Old(PSMarkSweep)收集器外別無選擇(還記得上面說過Parallel Scavenge收集器無法與CMS收集器配合工作嗎?)。由于老年代Serial Old收集器在服務(wù)端應(yīng)用性能上的“拖累”,使用了Parallel Scavenge收集器也未必能在整體應(yīng)用上獲得吞吐量最大化的效果,由于單線程的老年代收集中無法充分利用服務(wù)器多CPU的處理能力,在老年代很大而且硬件比較高級的環(huán)境中,這種組合的吞吐量甚至還不一定有ParNew加CMS的組合“給力”。直到Parallel Old收集器出現(xiàn)后,“吞吐量優(yōu)先”收集器終于有了比較名副其實的應(yīng)用組合,在注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器。


Parallel Old收集器的工作過程

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就非常符合這類應(yīng)用的需求。

從名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“標記—清除”算法實現(xiàn)的,它的運作過程相對于前面幾種收集器來說更復(fù)雜一些,整個過程分為4個步驟,包括:初始標記(CMS initial mark)

并發(fā)標記(CMS concurrent mark)

重新標記(CMS remark)

并發(fā)清除(CMS concurrent sweep)

其中,初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關(guān)聯(lián)到的對象,速度很快,并發(fā)標記階段就是進行GC RootsTracing的過程,而重新標記階段則是為了修正并發(fā)標記期間因用戶程序繼續(xù)運作而導(dǎo)致標記產(chǎn)生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比并發(fā)標記的時間短。由于整個過程中耗時最長的并發(fā)標記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作,所以,從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。通過圖3-10可以比較清楚地看到CMS收集器的運作步驟中并發(fā)和需要停頓的時間。


CMS收集器的運作步驟

G1收集器

G1(Garbage-First)收集器是當(dāng)今收集器技術(shù)發(fā)展的最前沿成果之一,早在JDK 1.7剛剛確立項目目標,Sun公司給出的JDK 1.7 RoadMap里面,它就被視為JDK 1.7中HotSpot虛擬機的一個重要進化特征。從JDK 6u14中開始就有EarlyAccess版本的G1收集器供開發(fā)人員實驗、試用,由此開始G1收集器的“Experimental”狀態(tài)持續(xù)了數(shù)年時間,直至JDK 7u4,Sun公司才認為它達到足夠成熟的商用程度,移除了“Experimental”的標識。

G1是一款面向服務(wù)端應(yīng)用的垃圾收集器。HotSpot開發(fā)團隊賦予它的使命是(在比較長期的)未來可以替換掉JDK 1.5中發(fā)布的CMS收集器。與其他GC收集器相比,G1具備如下特點。

并行與并發(fā):G1能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓的時間,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動作,G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行。

分代收集:與其他收集器一樣,分代概念在G1中依然得以保留。雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間、熬過多次GC的舊對象以獲取更好的收集效果。

空間整合:與CMS的“標記—清理”算法不同,G1從整體來看是基于“標記—整理”算法實現(xiàn)的收集器,從局部(兩個Region之間)上來看是基于“復(fù)制”算法實現(xiàn)的,但無論如何,這兩種算法都意味著G1運作期間不會產(chǎn)生內(nèi)存空間碎片,收集后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長時間運行,分配大對象時不會因為無法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC。

可預(yù)測的停頓:這是G1相對于CMS的另一大優(yōu)勢,降低停頓時間是G1和CMS共同的關(guān)注點,但G1除了追求低停頓外,還能建立可預(yù)測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經(jīng)是實時Java(RTSJ)的垃圾收集器的特征了。

在G1之前的其他收集器進行收集的范圍都是整個新生代或者老年代,而G1不再是這樣。使用G1收集器時,Java堆的內(nèi)存布局就與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區(qū)域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續(xù))的集合。

G1收集器之所以能建立可預(yù)測的停頓時間模型,是因為它可以有計劃地避免在整個Java堆中進行全區(qū)域的垃圾收集。G1跟蹤各個Region里面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經(jīng)驗值),在后臺維護一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃分內(nèi)存空間以及有優(yōu)先級的區(qū)域回收方式,保證了G1收集器在有限的時間內(nèi)可以獲取盡可能高的收集效率。

G1把內(nèi)存“化整為零”的思路,理解起來似乎很容易,但其中的實現(xiàn)細節(jié)卻遠遠沒有想象中那樣簡單,否則也不會從2004年Sun實驗室發(fā)表第一篇G1的論文開始直到今天(將近10年時間)才開發(fā)出G1的商用版。筆者以一個細節(jié)為例:把Java堆分為多個Region后,垃圾收集是否就真的能以Region為單位進行了?聽起來順理成章,再仔細想想就很容易發(fā)現(xiàn)問題所在:Region不可能是孤立的。一個對象分配在某個Region中,它并非只能被本Region中的其他對象引用,而是可以與整個Java堆任意的對象發(fā)生引用關(guān)系。那在做可達性判定確定對象是否存活的時候,豈不是還得掃描整個Java堆才能保證準確性?這個問題其實并非在G1中才有,只是在G1中更加突出而已。在以前的分代收集中,新生代的規(guī)模一般都比老年代要小許多,新生代的收集也比老年代要頻繁許多,那回收新生代中的對象時也面臨相同的問題,如果回收新生代時也不得不同時掃描老年代的話,那么Minor GC的效率可能下降不少。

在G1收集器中,Region之間的對象引用以及其他收集器中的新生代與老年代之間的對象引用,虛擬機都是使用Remembered Set來避免全堆掃描的。G1中每個Region都有一個與之對應(yīng)的Remembered Set,虛擬機發(fā)現(xiàn)程序在對Reference類型的數(shù)據(jù)進行寫操作時,會產(chǎn)生一個WriteBarrier暫時中斷寫操作,檢查Reference引用的對象是否處于不同的Region之中(在分代的例子中就是檢查是否老年代中的對象引用了新生代中的對象),如果是,便通過CardTable把相關(guān)引用信息記錄到被引用對象所屬的Region的RememberedSet之中。當(dāng)進行內(nèi)存回收時,在GC根節(jié)點的枚舉范圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

如果不計算維護Remembered Set的操作,G1收集器的運作大致可劃分為以下幾個步驟:

初始標記(Initial Marking)

并發(fā)標記(Concurrent Marking)

最終標記(Final Marking)

篩選回收(Live Data Counting andEvacuation)

對CMS收集器運作過程熟悉的讀者,一定已經(jīng)發(fā)現(xiàn)G1的前幾個步驟的運作過程和CMS有很多相似之處。初始標記階段僅僅只是標記一下GCRoots能直接關(guān)聯(lián)到的對象,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序并發(fā)運行時,能在正確可用的Region中創(chuàng)建新對象,這階段需要停頓線程,但耗時很短。并發(fā)標記階段是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時較長,但可與用戶程序并發(fā)執(zhí)行。而最終標記階段則是為了修正在并發(fā)標記期間因用戶程序繼續(xù)運作而導(dǎo)致標記產(chǎn)生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程Remembered Set Logs里面,最終標記階段需要把emembered Set Logs的數(shù)據(jù)合并到RememberedSet中,這階段需要停頓線程,但是可并行執(zhí)行。最后在篩選回收階段首先對各個Region的回收價值和成本進行排序,根據(jù)用戶所期望的GC停頓時間來制定回收計劃,從Sun公司透露出來的信息來看,這個階段其實也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因為只回收一部分Region,時間是用戶可控制的,而且停頓用戶線程將大幅提高收集效率。通過圖3-11可以比較清楚地看到G1收集器的運作步驟中并發(fā)和需要停頓的階段。


G1收集器運行示意
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容