《深入理解java虛擬機器》筆記3——7種垃圾收集器

weixin_34249678發表於2019-01-11

2019大三的寒假計劃——利用在公司每天早起的時間讀書,第一本是周志明老師的《深入理解Java虛擬機器——JVM高階特性與最佳實踐》,這一系列是通過對原文的拜讀與自己理解加上網路上的資料文章整理出的讀書筆記。

如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。**Java虛擬機器規範中對垃圾收集器應該如何實現並沒有任何規定,因此不同的廠商、版本的虛擬機器所提供的垃圾收集器都可能會有很大差別,並且一般都會提供引數供使用者根據自己的應用特點和要求組合出各個年代所使用的收集器。接下來討論的收集器基於JDK1.7 Update 14 之後的HotSpot虛擬機器(在此版本中正式提供了商用的G1收集器,之前G1仍處於實驗狀態),該虛擬機器包含的所有收集器如下圖所示:

<div align="center"> <img src="http://pic.yupoo.com/meandni/e8793494/27df7474.jpg" width=""/> </div>

以上是 HotSpot 虛擬機器中的 7 個垃圾收集器,連線表示垃圾收集器可以配合使用。

  • 單執行緒與多執行緒:單執行緒指的是垃圾收集器只使用一個執行緒進行收集,而多執行緒使用多個執行緒;
  • 序列與並行:序列指的是垃圾收集器與使用者程式交替執行,這意味著在執行垃圾收集的時候需要停頓使用者程式;並行指的是垃圾收集器和使用者程式同時執行。除了 CMS 和 G1 之外,其它垃圾收集器都是以序列的方式執行。

相關概念

並行和併發

  • 並行(Parallel):指多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。
  • 併發(Concurrent):指使用者執行緒與垃圾收集執行緒同時執行(但不一定是並行的,可能會交替執行),使用者程式在繼續執行。而垃圾收集程式執行在另一個CPU上。

吞吐量(Throughput)

吞吐量就是CPU用於執行使用者程式碼的時間CPU總消耗時間的比值,即

吞吐量 = 執行使用者程式碼時間 /(執行使用者程式碼時間 + 垃圾收集時間)。

假設虛擬機器總共執行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

Minor GC 和 Full GC

  • 新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為Java物件大多都具備朝生夕滅的特性,所以Minor GC非常頻繁,一般回收速度也比較快。具體原理見上一篇文章。
  • 老年代GC(Major GC / Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略裡就有直接進行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。

各收集器闡述

1. Serial 收集器

<div align="center"> <img src="http://pic.yupoo.com/meandni/5c861f07/b1ac068e.jpg" width=""/> </div>

Serial 翻譯為序列,也就是說它以序列的方式執行。

它是單執行緒的收集器,只會使用一個執行緒進行垃圾收集工作。

它的優點是簡單高效,對於單個 CPU 環境來說,由於沒有執行緒互動的開銷,因此擁有最高的單執行緒收集效率。

它是 Client 模式下的預設新生代收集器,因為在該應用場景下,分配給虛擬機器管理的記憶體一般來說不會很大。Serial 收集器收集幾十兆甚至一兩百兆的新生代停頓時間可以控制在一百多毫秒以內,只要不是太頻繁,這點停頓是可以接受的。

2. ParNew 收集器

<div align="center"> <img src="http://pic.yupoo.com/meandni/a5c6e277/481e1ca1.jpg" width=""/> </div>

它是 Serial 收集器的多執行緒版本。

是 Server 模式下的虛擬機器首選新生代收集器,除了效能原因外,主要是因為除了 Serial 收集器,只有它能與 CMS 收集器配合工作。

預設開啟的執行緒數量與 CPU 數量相同,可以使用 -XX:ParallelGCThreads 引數來設定執行緒數。

3. Parallel Scavenge 收集器

與 ParNew 一樣是多執行緒收集器。

其它收集器關注點是儘可能縮短垃圾收集時使用者執行緒的停頓時間,而它的目標是達到一個可控制的吞吐量,它被稱為“吞吐量優先”收集器。這裡的吞吐量指 CPU 用於執行使用者程式碼的時間佔總時間的比值。

停頓時間越短就越適合需要與使用者互動的程式,良好的響應速度能提升使用者體驗。而高吞吐量則可以高效率地利用 CPU 時間,儘快完成程式的運算任務,適合在後臺運算而不需要太多互動的任務。

縮短停頓時間是以犧牲吞吐量和新生代空間來換取的:新生代空間變小,垃圾回收變得頻繁,導致吞吐量下降。

可以通過一個開關引數開啟 GC 自適應的調節策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 區的比例、晉升老年代物件年齡等細節引數了。虛擬機器會根據當前系統的執行情況收集效能監控資訊,動態調整這些引數以提供最合適的停頓時間或者最大的吞吐量。

4. Serial Old 收集器

<div align="center"> <img src="http://pic.yupoo.com/meandni/14f72c5c/367020d3.jpg" width=""/> </div>

是 Serial 收集器的老年代版本,也是給 Client 模式下的虛擬機器使用。如果用在 Server 模式下,它有兩大用途:

  • 在 JDK 1.5 以及之前版本(Parallel Old 誕生以前)中與 Parallel Scavenge 收集器搭配使用。
  • 作為 CMS 收集器的後備預案,在併發收集發生 Concurrent Mode Failure 時使用。

5. Parallel Old 收集器

<div align="center"> <img src="http://pic.yupoo.com/meandni/2d1989e5/6e965e83.jpg" width=""/> </div>

是 Parallel Scavenge 收集器的老年代版本。

在注重吞吐量以及 CPU 資源敏感的場合,都可以優先考慮 Parallel Scavenge 加 Parallel Old 收集器。

6. CMS 收集器

<div align="center"> <img src="http://pic.yupoo.com/meandni/14f72c5c/367020d3.jpg" width=""/> </div>

CMS(Concurrent Mark Sweep),Mark Sweep 指的是標記 - 清除演算法。

分為以下四個流程:

  • 初始標記:僅僅只是標記一下 GC Roots 能直接關聯到的物件,速度很快,需要停頓。
  • 併發標記:進行 GC Roots Tracing 的過程,它在整個回收過程中耗時最長,不需要停頓。
  • 重新標記:為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,需要停頓。
  • 併發清除:不需要停頓。

在整個過程中耗時最長的併發標記和併發清除過程中,收集器執行緒都可以與使用者執行緒一起工作,不需要進行停頓。

具有以下缺點:

  • 吞吐量低:低停頓時間是以犧牲吞吐量為代價的,導致 CPU 利用率不夠高。
  • 無法處理浮動垃圾,可能出現 Concurrent Mode Failure。浮動垃圾是指併發清除階段由於使用者執行緒繼續執行而產生的垃圾,這部分垃圾只能到下一次 GC 時才能進行回收。由於浮動垃圾的存在,因此需要預留出一部分記憶體,意味著 CMS 收集不能像其它收集器那樣等待老年代快滿的時候再回收。如果預留的記憶體不夠存放浮動垃圾,就會出現 Concurrent Mode Failure,這時虛擬機器將臨時啟用 Serial Old 來替代 CMS。
  • 標記 - 清除演算法導致的空間碎片,往往出現老年代空間剩餘,但無法找到足夠大連續空間來分配當前物件,不得不提前觸發一次 Full GC。

7. G1 收集器

G1(Garbage-First),它是一款面向服務端應用的垃圾收集器,在多 CPU 和大記憶體的場景下有很好的效能。HotSpot 開發團隊賦予它的使命是未來可以替換掉 CMS 收集器。

堆被分為新生代和老年代,其它收集器進行收集的範圍都是整個新生代或者老年代,而 G1 可以直接對新生代和老年代一起回收。

<div align="center"> <img src="http://pic.yupoo.com/meandni/400d294e/1d022e13.png" width="600"/> </div>

G1 把堆劃分成多個大小相等的獨立區域(Region),新生代和老年代不再物理隔離。

<div align="center"> <img src="http://pic.yupoo.com/meandni/5393369d/5f8cbd42.png" width="600"/> </div>

通過引入 Region 的概念,從而將原來的一整塊記憶體空間劃分成多個的小空間,使得每個小空間可以單獨進行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預測的停頓時間模型成為可能。通過記錄每個 Region 垃圾回收時間以及回收所獲得的空間(這兩個值是通過過去回收的經驗獲得),並維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 Region。

每個 Region 都有一個 Remembered Set,用來記錄該 Region 物件的引用物件所在的 Region。通過使用 Remembered Set,在做可達性分析的時候就可以避免全堆掃描。

<div align="center"> <img src="http://pic.yupoo.com/meandni/ce8ef9fc/10c8999f.jpg" width=""/> </div>

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

  • 初始標記
  • 併發標記
  • 最終標記:為了修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機器將這段時間物件變化記錄線上程的 Remembered Set Logs 裡面,最終標記階段需要把 Remembered Set Logs 的資料合併到 Remembered Set 中。這階段需要停頓執行緒,但是可並行執行。
  • 篩選回收:首先對各個 Region 中的回收價值和成本進行排序,根據使用者所期望的 GC 停頓時間來制定回收計劃。此階段其實也可以做到與使用者程式一起併發執行,但是因為只回收一部分 Region,時間是使用者可控制的,而且停頓使用者執行緒將大幅度提高收集效率。

具備如下特點:

  • 空間整合:整體來看是基於“標記 - 整理”演算法實現的收集器,從區域性(兩個 Region 之間)上來看是基於“複製”演算法實現的,這意味著執行期間不會產生記憶體空間碎片。
  • 可預測的停頓:能讓使用者明確指定在一個長度為 M 毫秒的時間片段內,消耗在 GC 上的時間不得超過 N 毫秒。

總結

收集器 序列、並行or併發 新生代/老年代 演算法 目標 適用場景
Serial 序列 新生代 複製演算法 響應速度優先 單CPU環境下的Client模式
Serial Old 序列 老年代 標記-整理 響應速度優先 單CPU環境下的Client模式、CMS的後備預案
ParNew 並行 新生代 複製演算法 響應速度優先 多CPU環境時在Server模式下與CMS配合
Parallel Scavenge 並行 新生代 複製演算法 吞吐量優先 在後臺運算而不需要太多互動的任務
Parallel Old 並行 老年代 標記-整理 吞吐量優先 在後臺運算而不需要太多互動的任務
CMS 併發 老年代 標記-清除 響應速度優先 集中在網際網路站或B/S系統服務端上的Java應用
G1 併發 both 標記-整理+複製演算法 響應速度優先 面向服務端應用,將來替換CMS

參考資料

原文部落格:

《深入理解java虛擬機器》筆記3——7種垃圾收集器

相關文章