本文基于周志明的《深入理解java虛擬機 JVM高級特性與最佳實踐》所寫。特此推薦。
垃圾收集器是垃圾收集算法的具體實現(xiàn),Java虛擬機對垃圾收集器如何實現(xiàn)并未做規(guī)定,因此不同廠商會有各自的垃圾收集器。如HotSpot虛擬機中的收集器如下所示,其中存在連線的收集器即可搭配使用。 但現(xiàn)在沒有最好的收集器,都需要根據(jù)具體環(huán)境來選擇。

Serial收集器
Serial收集器是最基本、發(fā)展歷史最悠久的收集器。這個收集器是一個單線程的收集器,當它工作時必須暫停其他線程的工作,也就是Stop The World。這顯示是它的缺點, 這也是垃圾收集器一直努力的方向。當然,對于相比其它單線程收集器,Serial收集器簡單而高效。對于桌面應用來說,分配的管理內(nèi)存不會太多,停頓時間完全可以控制在幾十毫秒最多一百毫秒以內(nèi)。所以,Serial收集器對于運行在Client模式下的虛擬機來說是一個很好的選擇。下圖為Serial結(jié)合Serial Old收集器(后續(xù)介紹)的運行過程:

ParNew收集器
ParNew收集器其實就是Serial收集器的多線程版本(多CPU下使用效果較好),下圖為ParNew結(jié)合Serial Old收集器(后續(xù)介紹)的運行過程:

ParNew收集器對于Serial來說并沒有太多的創(chuàng)新之處,但它卻是許多運行在Server模式下的虛擬機中首選的新生代收集器,因為除了Serial收集器外,剩下只有它能與CMS收集器(后續(xù)介紹)配合工作了。所以,遺憾的是CMS作為老年代的收集器,卻無法與JDK1.4中已經(jīng)存在的新生代收集器Parallel Scavenge配合工作。
Parallel Scavenge收集器
Parallel Scavenge收集器是一個新生代收集器,它也是使用復制算法的收集器,又是并行的多線程收集器,看上去跟ParNew差不多。但是Parallel Scavenge收集器與其他收集器不同在于CMS等收集器的關(guān)注點在于盡可能地縮短垃圾收集時用戶線程的停頓時間,而Parallel Scavenge收集器的目標則是達到一個可控制的吞吐量(Throughput)。所謂的吞吐量就是CPU用于運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了100分鐘,其中垃圾收集花費1分鐘,那吞吐量就是99%。
Parallel Scavenge收集器分別可通過-XX:MaxGCPauseMillis參數(shù)控制最大垃圾收集停頓時間以及直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)。Parallel Scavenge收集器還有一個-XX:UseAdaptiveSizePolicy開關(guān)參數(shù),打開參數(shù)后就不需要手動指定新生代的大小、Eden與Survivor區(qū)的比例、晉升老年代對象年齡等細節(jié)參數(shù)了,虛擬機會根據(jù)當前系統(tǒng)的運行情況收集性能監(jiān)控信息,動態(tài)調(diào)整相關(guān)參數(shù)。這種調(diào)節(jié)方式叫自適應的調(diào)節(jié)策略,也是Parallel Scavenge收集器與ParNew收集器的一個重要區(qū)別。
Serial Old收集器
Serial Old是一個老年代收集器,它同樣是一個單線程收集器,使用的是“標記-整理”算法。這個收集器的主要意義也是在于給Client模式下的虛擬機使用。如果在Server模式下,那么它主要還有兩大用途:一種用途是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用;另一種用途就是作為CMS收集器的后備方案,在并發(fā)收集發(fā)生ConCurrent Mode Failure時使用。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲得最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務端上,這類應用尤其重視服務的響應速度,希望系統(tǒng)停頓時間最短,以給用戶帶來較好的體驗。從名字上就可以看出,CMS收集器是基于“標記-清除”算法實現(xiàn)的。但它的實際運作過程對于前面幾種收集器來說更復雜一些,整個過程分為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 Roots Tracing的過程;而重新標記階段則是為了修正并發(fā)標記期間用用戶程序繼續(xù)運作而導致標記產(chǎn)生的那一部分對象的標記記錄。這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比并發(fā)標記時間短。由于整個過程中耗時最長的并發(fā)標記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作,所以,從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的(注意并發(fā)與并行的概念),如下圖所示:

CMS是一款優(yōu)秀的收集器,但是還遠達不到的完美程度,它有以下3個明顯缺點:
- CMS收集器對CPU資源非常敏感。因為在并發(fā)階段,它會占用了一部分線程(或者說CPU資源)而導致應用程序變慢,總吞吐量會降低。
- CMS收集器無法處理浮動垃圾,可能出現(xiàn)“Concurrent Mode Failure”失敗而導致另一次Full GC的產(chǎn)生。由于CMS并發(fā)清理階段用戶線程還在運行著,伴隨著程序運行自然就還會有新的垃圾不斷產(chǎn)生,這部分垃圾出現(xiàn)在標記過程之后,CMS無法在當次收集中處理它們,只好留待在下一次GC時再清理掉,這一部分垃圾就稱為“浮動垃圾”。
- 還有最后一點,CMS是一款基于“標記-清除”算法實現(xiàn)的收集器,這意味著收集結(jié)束時會有大量的空間碎片產(chǎn)生。為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullColletion開關(guān)參數(shù)(默認是開啟的),用于在CMS收集器頂不住要進行Full GC時開啟內(nèi)存碎片合并整理過程,內(nèi)存整理的過程是無法并發(fā)的,空間的碎片問題沒有了,但停頓的時間不得不變長了。虛擬機設(shè)計者還提供了另外一個參數(shù)-XX:CMSFullGCsBeforeCompaction,這個參數(shù)是用于設(shè)置執(zhí)行多少次不壓縮的Full GC后,跟著來一次帶壓縮的(默認值為0,表示每次進入Full GC時都進行碎片整理)。
G1收集器
G1(Garbage First)收集器是當今收集器技術(shù)發(fā)展的最前沿成果之一,從JDK 6u14中開始就有Early Acsess版本的G1收集器供開發(fā)人員實驗、試用,由此開始G1收集器的 “Experimental” 狀態(tài)持續(xù)了數(shù)年時間,直到JDK7u4,Sun公司才認為它達到足夠成熟的商用程度,移除了“Experimental”的標識。G1是一款面向服務端應用的垃圾收集器。HotSpot開發(fā)團隊賦予它的使命是未來可以替換掉JDK1.5中發(fā)布的CMS收集器。其與其它收集器相比,G1具備如下特點:
- 并行與并發(fā):和CMS類似。
- 分代收集:分代概念在G1中依然得以保留。雖然G1可以不需要其它收集器配合就能獨立管理整個GC堆,但它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間、熬過多次GC的舊對象以獲取更好的收集效果。也就是說G1可以自己管理新生代和老年代了。
- 空間整合:由于G1使用了獨立區(qū)域(Region)概念,G1從整體來看是基于“標記-整理”算法實現(xiàn)收集,從局部(兩個Region)上來看是基于“復制”算法實現(xiàn)的,但無論如何,這兩種算法都意味著G1運作期間不會產(chǎn)生內(nèi)存空間碎片。
- 可預測的停頓:這是G1相對于CMS的另一大優(yōu)勢,降低停頓時間是G1和CMS共同的關(guān)注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用這明確指定一個長度為M毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不得超過N毫秒。
與其它收集器相比,G1變化較大的是它將整個Java堆劃分為多個大小相等的獨立區(qū)域(Region),雖然還保留了新生代和來年代的概念,但新生代和老年代不再是物理隔離的了它們都是一部分Region(不需要連續(xù))的集合。同時,為了避免全堆掃描,G1使用了Remembered Set來管理相關(guān)的對象引用信息。當進行內(nèi)存回收時,在GC根節(jié)點的枚舉范圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏了。
如果不計算維護Remembered Set的操作,G1收集器的運作大致可劃分為以下幾個步驟:
- 初始標記(Initial Making)
- 并發(fā)標記(Concurrent Marking)
- 最終標記(Final Marking)
- 篩選回收(Live Data Counting and Evacuation)
看上去跟CMS收集器的運作過程有幾分相似,不過確實也這樣。初始階段僅僅只是標記一下GC Roots能直接關(guān)聯(lián)到的對象,并且修改TAMS(Next Top Mark Start)的值,讓下一階段用戶程序并發(fā)運行時,能在正確可以用的Region中創(chuàng)建新對象,這個階段需要停頓線程,但耗時很短。并發(fā)標記階段是從GC Roots開始對堆中對象進行可達性分析,找出存活對象,這一階段耗時較長但能與用戶線程并發(fā)運行。而最終標記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這階段需要停頓線程,但可并行執(zhí)行。最后篩選回收階段首先對各個Region的回收價值和成本進行排序,根據(jù)用戶所期望的GC停頓時間來制定回收計劃,這一過程同樣是需要停頓線程的,但Sun公司透露這個階段其實也可以做到并發(fā),但考慮到停頓線程將大幅度提高收集效率,所以選擇停頓。下圖為G1收集器運行示意圖:
