深入理解 JVM 之 垃圾收集器

TimberLiu發表於2019-02-25

上一篇文章中學習了 JVM 的垃圾回收機制,和記憶體分配和回收策略。不過這都是一些理論知識,這篇文章中會學習一下 HotSpot 虛擬機器中的垃圾收集器,這都是垃圾回收理論的具體實現。

垃圾收集器

HotSpot 虛擬機器中有多種收集器,不同的收集器特點也不同,各年代使用的收集器也可以根據應用的特點和要求進行組合。

Serial 收集器

Serial 收集器是一個單執行緒的收集器,它不僅只會使用一個 CPU 或一條收集執行緒去完成垃圾收集工作,而且在垃圾收集時,必須暫停其他所有的工作執行緒,直到它收集結束。

Serial 收集器是 HotSpot 虛擬機器在執行 Client 模式下的預設新生代收集器。在垃圾收集時,年輕代使用“複製”演算法,老年代使用“標記-整理”演算法。

但它也有優點,與其他收集器的單執行緒相比,由於沒有現成互動的開銷,專心做垃圾收集,所以其簡單而高效;

可以使用 -XX:UseSerialGC 引數選擇使用 Serial 收集器,此時年輕代採用 Serail,老年代採用 Serial Old

ParNew 收集器

ParNew 收集器是 Serial 收集器的多執行緒版本,除了使用多執行緒進行垃圾回收外,其他幾乎一樣。

它是許多執行在 Server 模式下的虛擬機器首選的新生代收集器,一個很重要的原因是除了 Serial 收集器外,只有 ParNew 收集器與 CMS 收集器配合工作。垃圾收集時,年輕代使用“複製”演算法,老年代使用“標記-整理”演算法。

ParNew 收集器預設開啟的執行緒數與 CPU 的數量相同,可以使用 -XX:ParallelGCThreas 引數來限制垃圾收集的執行緒數。使用 -XX:UseParNewGC 引數來使用 ParNew 收集器。

Parallel Scavenge 收集器

Parallel Scavenge 收集器是新生代收集器,也是使用複製演算法的多執行緒收集器。與其他收集器不同的是,它關注的是達到一個可控制的吞吐量,吞吐量 = 執行程式碼時間 / (執行程式碼時間 + 垃圾收集時間)。

Parallel Scanenge 收集器提供了兩個引數用於精確控制吞吐量。第一個是控制最大垃圾收停頓時間的 -XX:MaxGCPauseMillis 引數。它允許是一個大於 0 的毫秒數,收集器將盡可能保證記憶體回收時間不超過設定值。這個引數也不是越小越好,GC 停頓時間縮短是以犧牲吞吐量和新生代空間為代價換取的。

第二個是直接設定吞吐量大小的 -XX:GCTimeRatio 引數。它允許是一個大於 0 且小於 100 的整數,就是垃圾收集時間佔總時間的比率。

另外,Parallel Scavenge 收集器擁有自適應調節機制,它不需要手工指定新生代的大小(-Xmn)、EdenSurvivor 區的比例(-XX:SurvivorRatio)、晉升老年代物件大小 -XX:PretenureSizeThreshold 等細節引數,虛擬機器會根據當前系統的執行情況收集效能監控資訊,動態調整引數以提供最合適的停頓時間及最大的吞吐量。可使用 -XX:UseAdaptiveSizePolicy 引數來開啟。

Serial Old 收集器

Serial OldSerial 收集器的老年代版本,是一個單執行緒收集器,使用“標記-整理演算法”。

Parallel Old 收集器

Parallel OldParallel Scavenge 收集器的老年代版本,使用多執行緒和“標記-整理”演算法。如果新生代選擇了 Parallel Scavenge 收集器,老年代只能選擇 Serial Old 收集器。

CMS 收集器

CMS(Concurrent Mark Sweep) 是一種以獲取最短停頓時間為目標的收集器,使用“標記-清除”演算法。如果應用重視響應速度,希望停頓時間最短,就可以選擇 CMS 收集器。

它的運作過程可分為 4 個步驟:

  • 初始標記:僅僅標記 GC Roots 能直接關聯到的物件,速度很快,需要 Stop the world
  • 併發標記:進行 GC Roots Tracing 過程。
  • 重新標記:修正併發標記期間因使用者程式執行而導致標記產生變動的物件的標記記錄。
  • 併發清除。

CMS 的主要優點是併發收集、低停頓。但也有三個缺點:

  • CPU 資源非常敏感。併發階段雖不會導致使用者執行緒停頓,但會因為佔用資源而導致程式變慢,總吞吐量降低。
  • 無法處理浮動垃圾,也就是在標記過程後,清除階段產生但當次收集中不能處理的垃圾,可能出現 Concurrent Mode Failure 失敗而導致另一次 Full FC 的產生。
  • CMS 基於標記-清除演算法,收集結束時會產生大量空間碎片。碎片過多時,無法找到足夠的連續空間來分配大物件,不得不提前出發一次 Full GC

可以使用 -XX:UseConcMarkSweepGC 引數來選擇 CMS 收集器。

G1 收集器

G1(Garbage-First) 是一款面向服務端應用的垃圾收集器。它的特點如下:

  • 並行與併發:充分利用多 CPU、多核環境,使用多個 CPU 來縮短停頓的時間,部分需要其他收集器原本需要停頓 Java 執行緒執行的 GC 動作,G1 收集器可通過併發的方式讓 Java 程式繼續執行。
  • 分代收集:G1 收集器能獨立管理整個 GC 堆,並且能採用不同的方式處理不同時期的物件。
  • 空間整合:G1 收集器從整體來看,基於“標記-整理”演算法實現;從區域性來看,基於“複製”演算法實現。
  • 可預測的停頓:G1 能明確指定垃圾收集的限制時間。

使用 G1 收集器時,將 Java 堆劃分為多個大小相等的區域 RegionG1 跟蹤各個 Region 的回收價值和成本(回收獲得空間及回收時間),後臺會維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 Region。它通過使用 Remembered Set 來避免全堆掃描。

G1 收集器的執行步驟可分為:

  • 初始標記:僅僅標記一下 GC Roots 直接能關聯到的物件,需要停頓,但耗時很短。
  • 併發標記:從 GC Roots 開始對堆中物件進行可達性分析,找出存活的物件,耗時較長,但可併發執行。
  • 最終標記:修正併發標記期間因使用者程式執行而導致標記產生變動的物件的標記記錄。
  • 篩選回收:對各個 Region 的回收價值和成本進行排序,根據指定的 GC 停頓時間制定回收計劃。

常用收集器組合

HotSpot 虛擬機器中包含了七種垃圾收集器,如下圖:

深入理解 JVM 之 垃圾收集器

它們的組合說明如下:

新生代收集器 年老代收集器 說明
Serial Serial Old 都是單執行緒,GC 時會暫停所有應用執行緒。
使用 -XX:+UseSerialGC 選項來開啟
Serial CMS + Serial Old CMS 是併發 GC,不需要暫停所有應用執行緒。
當 CMS 進行 GC 失敗時,會自動使用 Serial Old 策略進行 GC
使用 -XX:+UseConcMarkSweepGC 選項來開啟
ParNew CMS ParNew 是 Serial 的並行版本,可以指定 GC 執行緒數
預設 GC 執行緒數為 CPU 的數量
ParNew Serial Old 使用 -XX:+UseParNewGC 選項來開啟
Parallel Scavenge Serial Old Parallel Scavenge 策略關注吞吐量,適用於後臺持久執行的應用程式
使用 -XX:+UseParallelGC 選項來開啟
Parallel Scavenge Parallel Old Parallel Old 是 Serial Old 的並行版本
使用 -XX:+UseParallelOldGC 選項來開啟
G1GC G1GC -XX:+UseG1GC #開啟
-XX:MaxGCPauseMillis #暫停時間目標

參考資料

相關文章