垃圾收集3: 垃圾回收器
如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。現在為止還沒有最好的垃圾收集器出現,更加沒有萬能的垃圾收集器,我們能做的就是根據具體應用場景選擇適合自己的垃圾收集器。HotSpot虛擬機器中的7個垃圾收集器如下所示:
如果兩個收集器存在連線,說明可以搭配使用。所處的區域表示屬於新生代還是老年代收集器。- 單執行緒與多執行緒:單執行緒指的是垃圾收集器只使用一個執行緒進行收集,而多執行緒使用多個執行緒;
- 序列與並行:序列指的是垃圾收集器與使用者程式交替執行,這意味著在執行垃圾收集的時候需要停頓使用者程式;並行指的是垃圾收集器和使用者程式同時執行。除了 CMS 和 G1 之外,其它垃圾收集器都是以序列的方式執行。
Serial 收集器
以序列的方式執行,是單執行緒的收集器,只會使用一個執行緒來進行垃圾收集工作。新生代採用複製演算法,老年代採用標記-整理演算法。
它的優點是簡單高效,對於單個 CPU 環境來說,由於沒有執行緒互動的開銷,因此擁有最高的單執行緒收集效率。
它是Client模式下的預設新生代收集器,因為在該應用場景下,分配給虛擬機器管理的記憶體一般來說不會很大。Serial 收集器收集幾十兆甚至一兩百兆的新生代停頓時間可以控制在一百多毫秒以內,只要不是太頻繁,這點停頓是可以接受的。
ParNew收集器
是Serial收集器的多執行緒版本。除了使用多執行緒進行垃圾收集外,其餘行為(控制引數、收集演算法、回收策略等等)和Serial收集器完全一樣。新生代採用複製演算法,老年代採用標記-整理演算法。
是Server模式下的虛擬機器首選新生代收集器,除了效能原因外,主要是因為除了 Serial 收集器,只有它能與 CMS 收集器配合工作。
在單CPU的環境中不會有比Serial收集器更好的效果。但是隨著使用的CPU的數量的增加,對於GC時系統資源的有效利用是有好處的。預設開啟的執行緒數量與 CPU 數量相同,可以使用 -XX:ParallelGCThreads 引數來設定執行緒數。
並行:多條垃圾收集執行緒並行工作,單此時使用者執行緒仍然處於等待狀態。
併發:使用者執行緒與垃圾收集執行緒同時執行,不一定是並行的。使用者程式在繼續執行,GC程式執行在另外一個CPU上。
Parallel Scavenge收集器
類似於ParNew收集器,是一個並行的多執行緒收集器、新生代收集器,使用複製演算法。
其它收集器關注點是儘可能縮短垃圾收集時使用者執行緒的停頓時間,而它的目標是達到一個可控制的吞吐量,它被稱為“吞吐量優先”收集器。這裡的吞吐量指 CPU 用於執行使用者程式碼的時間佔總時間的比值。
停頓時間越短就越適合需要與使用者互動的程式,良好的響應速度能提升使用者體驗。而高吞吐量則可以高效率地利用 CPU 時間,儘快完成程式的運算任務,適合在後臺運算而不需要太多互動的任務。
縮短停頓時間是以犧牲吞吐量和新生代空間來換取的:新生代空間變小,垃圾回收變得頻繁,導致吞吐量下降。
Serial Old收集器
Serial收集器的老年代版本,它同樣是一個單執行緒收集器。它主要有兩大用途:一種用途是在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用,另一種用途是作為CMS收集器的後備方案,在併發收集發生 Concurrent Mode Failure 時使用。
Parallel Old收集器
是 Parallel Scavenge 收集器的老年代版本,使用多執行緒和“標記-整理”演算法。
在注重吞吐量以及 CPU 資源敏感的場合,都可以優先考慮 Parallel Scavenge 加 Parallel Old 收集器。
CMS收集器
CMS(Concurrent Mark Sweep),Mark Sweep指的是標記-清除演算法。是一種以獲取最短回收停頓時間為目標的收集器。因而非常符合在注重使用者體驗的應用上使用。
是HotSpot虛擬機器第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集執行緒與使用者執行緒(基本上)同時工作。
分為以下四個流程:
- 初始標記:僅僅只是標記以下GC Roots能直接關聯到的物件。速度很快,需要停頓。
- 併發標記:進行GC Root Tracing的過程,同時開啟GC和使用者執行緒,用一個閉包結構去記錄可達物件。但在這個階段結束,這個閉包結構並不能保證包含當前所有的可達物件。因為使用者執行緒可能會不斷的更新引用域,所以GC執行緒無法保證可達性分析的實時性。所以這個演算法裡會跟蹤記錄這些發生引用更新的地方。在整個回收過程中耗時最長,不需要停頓。
- 重新標記:為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長,但遠比並發標記的時間段。
- 併發清除: 開啟使用者執行緒,同時GC執行緒開始對為標記的區域做清掃。
主要優點:併發收集、低停頓
具有以下三個缺點:
- 對CPU資源敏感,吞吐量低。併發階段雖然不會導致使用者執行緒停頓,但是會因為佔用了一部分CPU資源導致引用程式變慢,總吞吐量降低。
- 無法處理浮動垃圾,可能出現Concurrent Mode Failure。浮動垃圾是指併發清除階段由於使用者執行緒繼續執行而產生的垃圾,這部分垃圾只能到下一次 GC 時才能進行回收。由於浮動垃圾的存在,因此需要預留出一部分記憶體,意味著 CMS 收集不能像其它收集器那樣等待老年代快滿的時候再回收。如果預留的記憶體不夠存放浮動垃圾,就會出現 Concurrent Mode Failure,這時虛擬機器將臨時啟用 Serial Old 來替代 CMS。
- 使用“標記-清除”演算法會導致收集結束時有大量空間碎片產生。往往出現老年代空間剩餘,但無法找到足夠大連續空間來分配當前物件,不得不提前觸發一次 Full GC。
G1收集器
G1 (Garbage-First)是一款面向伺服器的垃圾收集器,主要針對配備多顆處理器及大容量記憶體的機器,在多 CPU 和大記憶體的場景下有很好的效能,以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量效能特徵,被視為JDK1.7中HotSpot虛擬機器的一個重要進化特徵。HotSpot 開發團隊賦予它的使命是未來可以替換掉 CMS 收集器。
具有以下特點:
- 並行與併發:G1能充分利用CPU、多核環境下的硬體優勢,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓時間。部分其他收集器原本需要停頓Java執行緒執行的GC動作,G1收集器仍然可以通過併發的方式讓java程式繼續執行。
- 分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念。
- 空間整合:與CMS的“標記--清理”演算法不同,G1從整體來看是基於“標記整理”演算法實現的收集器;從區域性上來看是基於“複製”演算法實現的,這意味著執行期間不會產生記憶體空間碎片。
- 可預測的停頓:這是G1相對於CMS的另一個大優勢,降低停頓時間是G1 和 CMS 共同的關注點,但G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為 M 毫秒的時間片段內,消耗在 GC 上的時間不得超過 N 毫秒。
其它收集器進行收集的範圍都是整個新生代或者老年代,而 G1 可以直接對新生代和老年代一起回收。
G1 把堆劃分成多個大小相等的獨立區域(Region),新生代和老年代不再物理隔離。 通過引入 Region 的概念,從而將原來的一整塊記憶體空間劃分成多個的小空間,使得每個小空間可以單獨進行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預測的停頓時間模型成為可能。通過記錄每個Region垃圾回收時間以及回收所獲得的空間(這兩個值是通過過去回收的經驗獲得),並維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 Region。每個 Region 都有一個 Remembered Set,用來記錄該 Region 物件的引用物件所在的 Region。通過使用 Remembered Set,在做可達性分析的時候就可以避免全堆掃描。
如果不計算維護 Remembered Set 的操作,G1 收集器的運作大致可劃分為以下幾個步驟:- 初始標記:標記GC Roots能直接關聯的物件,需要停頓,時間短
- 併發標記:從GC Root開始對堆中物件進行可達性分析,耗時長
- 最終標記:為了修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機器將這段時間物件變化記錄線上程的 Remembered Set Logs 裡面,最終標記階段需要把 Remembered Set Logs 的資料合併到 Remembered Set 中。這階段需要停頓執行緒,但是可並行執行。
- 篩選回收:首先對各個 Region 中的回收價值和成本進行排序,根據使用者所期望的 GC 停頓時間來制定回收計劃。此階段其實也可以做到與使用者程式一起併發執行,但是因為只回收一部分 Region,時間是使用者可控制的,而且停頓使用者執行緒將大幅度提高收集效率。