//FIX
這里主要向大家介紹目前HostSpot虛擬機中有哪些垃圾收集器,各自的特點。并分析各個垃圾收集的使用場景。
目前還沒有哪一種收集器是萬能的,只能說哪種收集器更適用哪些場景。
1. Serial收集器
Serial(串行)收集器是最先出現的收集器。這是一個單線程的收集器,并且在收集的過程中會暫停其他工作線程,直到收集結束。這種在垃圾收集的時候暫停其他一切工作線程的情況叫做“Stop the World”,這種暫停一切工作線程對于很多應用是難以接受的,比如你使用一個應用,每用1小時就要暫停幾分鐘,這對于用戶體驗是非常糟糕的。所以我們要盡量減少出現這種“Stop the World”的情況。
對于“Stop the World”有一個很形象的比喻:程序在堆或者方法區中創建對象相當于你在屋子里制造垃圾,而垃圾收集器就相當于有一個保姆在給你打掃垃圾。在打掃垃圾的時候你還在往地上亂扔垃圾,這房間就很難打掃完。(一般懶的人會說我家在打掃屋子的時候就是一邊打掃一邊扔啊,這種后面會有講到怎么讓你一邊扔垃圾一邊打掃)
雖然這種比喻不是很準確,但“Stop the world”就是在讓你不要在修改對象之間的引用了。
目前只有在Client模式下會默認是新生代收集器。雖然使用這種收集器會出現長時間的停頓,但相比于其他的收集器也有其優點,那就是可以專注于一件事情,沒有切換線程的開銷。在桌面應用程序的場景中,分配給虛擬機管理的內存通常來說不會很多。一般就幾十兆或者說在多一點一兩百兆的新生代。只要停頓時間可以控制在幾十毫秒或者一百多毫秒以內的話,這點停頓是可以接受的。(在平時玩游戲的延遲都會在幾十毫秒以內,這樣對于玩家是完全可以接受的,同理對于幾十毫秒的停頓對于用戶來說也是一樣)所以,Serial收集器對于在Client模式下的虛擬機來說是一個很好的選擇。
2.ParNew收集器
簡單來說ParNew收集器就是Serial收集器的多線程版本。
ParNew收集器沒有什么創新之處,但它卻是許多Server模式下的首選的新生代收集器,其中有一個很重要的原因就是目前還有他能和CMS收集器配合工作。(CMS收集器是一款具有跨時代意義的垃圾收集器,稍后會做介紹)目前CMS作為年老代的收集器,新生代只能選擇Serial收集器或者是ParNew收集器。
由于ParNew收集器是多線程的,所以現在單CPU的服務器上是沒有Serial收集器的效果好的,但是隨著CPU數量的提升,ParNew收集器的優勢就會體現出來了。ParNew收集器默認開啟的收集線程數是和CPU的數量是相同的,我們也可用-XX:ParallelGCThreads這個參數來制定線程數。
3. Parallel Scavenge 收集器
也是一個新生代收集器,也是使用的復制算法,也是并行的多線程收集器。。。那么它有什么特別之處呢?
它的特點是它的關注點和其他收集器不同,其他的收集器都要盡量的縮短垃圾回收的停頓時間,而Parallel Scavenge收集器則是希望到達一個可以控制的吞吐量。在垃圾收集的過程中吞吐量是指:用戶運行代碼時間/(運行代碼時間+垃圾收集時間)。
停頓時間短適合那些需要和用戶交互的程序,然而吞吐量高的則是合適那些在后臺運算不需要太多交互的程序。
Parallel Scavenge提供了兩個參數來控制吞吐量,一個是最大垃圾回收停頓時間-XX:MaxGCPauseMillis和吞吐量大小-XX:GCTimeRatio參數。ParallelScavenge收集器會盡量滿足你設置的這兩個值。
Parallel Scavenge收集器有一個很重要的參數是:-XX:+UseAdaptiveSizePolicy。這是一個開關參數,如果打開之后,就不需要你自己配置新生代大小,Eden區大小和Survivor區大小和晉升老年代年齡等參數了。收集器會根據你虛擬機實際運行情況來自行調整,以找到適合的最大吞吐量或者是停頓時間。
4. Serial Old 收集器
就是Serial老年代版本,單線程,使用標記-整理算法。
5. Parallel Old 收集器
Parallel Old 是Parallel Scavenge收集器的老年代版本,使用的是標記整理算法,多線程。在JDK1.6中才開始提供的。在此之前如果年輕代用了Parallel Scavenge收集器,那么在老年代中只能使用Serial Old收集器。這樣的組合在多核CPU上的吞吐量未必能有ParNew+CMS組合給力。
在Parallel Old收集器出現后,在吞吐量優先上就可以優先考慮Parallel組合的收集器了。
6. CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種哦以獲取最短回收停頓時間為目標的收集器。最使用的場景就是Java的B/S系統的服務器上。B/S架構的程序尤其要重視服務器的響應速度,相應速度越短則用戶體驗就越好。
CMS是基于標記-清除算法的,它的收集過程相對復雜一些,主要分四個步驟:
- 初始標記:僅僅是標記一下GC Roots能直接關聯到的對象。速度很快。
- 并發標記:通過GC Roots來遍歷對象的過程。這段期間是允許用戶程序繼續運行的。
- 重新標記:修正并發標記期間因用戶程序運行而導致變動的那一部分標記記錄。
- 并發清除:就是對沒有被引用的對象進行清除。
首先,執行最長時間的是并發標記和并發清除這兩個階段,由于這兩個階段都是可以和用戶程序一起執行的。雖然在初始標記和重新標記期間還是需要“Stop The World”。但這兩個階段都是很快的,總體來說CMS收集器是一個低停頓的收集器。
但CMS收集器也有它的缺點:
- 第一個缺點是CMS收集器很依賴CPU資源。CMS默認啟動的回收線程數是(CPU數量+3)/ 4,那么在CPU在4個以上的時候需要占用四分之一以上的CPU資源,CPU越多占用的就越少。但如果CPU數量就兩個的話就會占用一般的CPU資源,這是讓人無法接受的。
- CMS收集器可能會出現“Concurrent Mode Failure”錯誤,而導致產生一次Full GC。這是由于在并發清理階段用戶程序還在運行,這時候產生的垃圾就無法被清除掉,只好留給下一次GC的時候在清除掉。如果是用戶程序產生的垃圾速度要更快一些,導致要填滿了老年代,那么用戶的程序就因為沒有內存而無法運行了。所以在CMS收集器中需要預留一部分空間來當做并發收集時程序所使用的。在JDK1.5的時候是當老年代使用超過了68%就會進行回收。在1.6中CMS收集器啟動的閥值默認已經提升至92%了。如果預留的不夠了就會出現“Concurrent Mode Failure”錯誤。這時候虛擬機就會啟動預備方案,使用Serial Old收集器來進行老年代的回收。這樣停頓的時間就變長了。
- 第三個缺點就是CMS是基于“標記-清除”來實現的。所以在清除的時候會產生大量的內存碎片,給分配大對象帶來了很大的麻煩。如果無法分配一個大對象就不得不進行一次Full GC。為了這個缺點,設計者提供了一個參數,來設置執行多少次不壓縮的Full GC后跟著來一次壓縮的GC。