九款常見的JVM 垃圾回收器

2024.03.11

JVM 不僅是大廠面試的一個高頻問題,也是Java程式設計師跨入高階必須掌握的知識點,垃圾回收器作為JVM中核心的一環,了解它的原理,可以幫助我們更好地調優和故障排除,因此,今天我們就來聊聊JVM中9款常見的垃圾回收器。

背景 

因為Java虛擬機的類型比較多,如果沒有特別說明,本文特別指HotSpot虛擬機,在分享回收器之前,我們先對HotSpot 虛擬機背景做個簡單的介紹。

HotSpot VM,最初是由「Longview Technologies」 這家小公司設計,一開始也不是為Java語言研發。

1997年,Sun公司收購了這家公司,因此也得到了HotSpot虛擬機,在Sun公司的一番優化下,HotSpot 虛擬機就成了Sun/OracleJDK 和OpenJDK共同的預設虛擬機。

2010年,Oracle 收購Sun公司,HotSpot 虛擬機器也就順理成章成為了Oracle旗下產品。

Sun/OracleJDK 和OpenJDK 都是Oracle 旗下產品,Sun/OracleJDK 是商用版,OpenJDK 是免費版,兩款虛擬機器的核心是一樣,只是功能略有差異。

關於使用的是 Sun/OracleJDK 還是 OpenJDK ,可以透過 java -version 指令查看。

Sun/OracleJDK:

OpenJDK:

1.串行 

Serial 收集器,見名知意,它是一個單線程的收集器,而且在進行垃圾回收時還必須暫停其它的工作線程,直到它收集結束(Stop The World)。

在JDK 1.3.1 之前,它是HotSpot虛擬機年輕代收集器的唯一選擇。

Serial(年輕代) 和Serial Old(老年代) 組合模式下,收集器大致的工作流程如下圖:

儘管Serial 收集器是單線程回收,並且會暫停其它的工作線程,看起來性能很差,但是,它仍然是HotSpot 虛擬機運行在客戶端模式下的默認新生代收集器,因為相對於其它收集器的單線程,Serial 收集器消耗的記憶體最低,加上沒有多線程互動的開銷,反而使得它簡單有效率。

在啟動Java程序時,可以透過設定 -XX:+UseSerialGC -XX:+UseSerialOldGC 參數,使用上述回收器組合。

2.標準桿新

ParNew 收集器是Serial 收集器的多執行緒並行版本,除了使用多執行緒進行垃圾回收之外,其它的行為和Serial 收集器都是相同的。主要應用在HotSpot虛擬機器運行在服務端模式下的場景。

ParNew(年輕代) 和Serial Old(老年代) 組合模式下,收集器大致的工作流程如下圖:

在啟動 Java進程時,可以透過設定 -XX:+UseParNewGC -XX:+UseSerialOldGC 參數,使用上述回收器組合。

3.並行清除 

Parallel Scavenge 收集器也是一款用於年輕代的回收器,它和ParNew 收集器一樣,採用多執行緒並發回收,但是,Parallel Scavenge可以透過-XX:MaxGCPauseMillis 參數設定GC的最大停頓時間,這樣就可以達到一個吞吐量(Throughput)可控的目標,從而優於ParNew回收器。

Parallel Scavenge(年輕代) 和Serial Old(老年代) 組合模式下,收集器大致的工作流程如下圖:

在啟動 Java進程時,可以透過設定 -XX:+UseParallelGC -XX:+UseSerailOldGC 參數,使用上述回收器組合。

但是,這種組合看起來很尷尬,年輕代使用的多線程並發收集,而老年代卻使用單線程進行回收,怎麼看起來老年代的回收都是“拖累”,因此,用於老年代的Parallel Old 並發收集器就誕生了。

Parallel Scavenge(年輕代) 與Parallel Old(老年代) 組合模式下,收集器大致的工作流程如下圖:

圖片

啟動 Java進程時,可以透過設定 -XX:+UseParallelGC -XX:+UseParallelOldGC 參數,使用上述回收器組合。

4.舊系列 

Serial Old 收集器是Serial 的老年代版本,它也是一個單線程收集器,使用'標記-整理'演算法,和Serial 收集器一樣也是用於HotSpot客戶端模式。

Serial(年輕代) 和Serial Old(老年代) 組合模式下,收集器大致的工作流程如下圖:

在啟動Java程序時,可以透過設定 -XX:+UseSerialGC -XX:+UseSerialOldGC 參數,使用上述回收器組合。

5.平行老

Parallel Old 收集器是從JDK 6 開始提供支援的,它是Parallel Scavenge 收集器的老年代版本,支援多執行緒並發收集,採用'標記-整理'演算法。 Parallel Old 收集器的出現,真正意義上實現了「吞吐量優先」的目標。

Parallel Scavenge(年輕代) 與Parallel Old(老年代) 組合模式下,收集器大致的工作流程如下圖:

啟動 Java進程時,可以透過設定 -XX:+UseParallelGC -XX:+UseParallelOldGC 參數,使用上述回收器組合。

6.內容管理系統

CMS 收集器,從JDK5發布之後正式誕生,可以毫不誇張地說:CMS是一個跨時代的收集器,曾幾何時,它是各互聯網大廠面試中垃圾回收器的必問知識點。

CMS 是Comcurrent Mark Sweep 的簡稱,用於老年代的垃圾回收。 CMS的收集過程包含5個步驟:

  • Initial Mark(初始標記) Stop The World
  • Concurrent Marking(並發標記)
  • Remark(重複標記) Stop The World
  • Concurrent Sweep(並發清除)
  • Resetting(重置)

CMS 收集器大致的工作流程如下圖:

儘管CMS回收器實現了回收線程與應用線程能同時並發工作的目標,但它也有致命的問題:無法處理“浮動垃圾”,有可能出現Concurrent Mode Failure 失敗,導致Full GC。因此,Oracle官方目前已經將CMS 申明為“deprecated”,不建議使用。這也宣告了CMS收集器的歷史使命已結束。 

啟動 Java進程時,可以透過設定-XX:+UseConcMarkSweepGC 參數,顯示使用CMS回收器。

7.G1 

G1 回收器是Garbage First 的簡稱, 它是一款面向伺服器的垃圾回收器,用於大內存的多處理器計算機,目標是實現低延時垃圾回收。

從Oracle JDK 7 Update 4 及更高版本已完全支援G1,並且 JDK9 開始,G1 已經成為了預設的垃圾收集器。

應該說,G1是垃圾回收器歷史上的一個里程碑,開啟了基於Region回收的時代,和以往的垃圾回收器不一樣,G1儘管依然保留了年輕代和老年代的概念,但是各代存儲地址是不連續的,每一代包含了n個大小相同且不連續的Region,G1 的堆內存分配如下圖:

G1提供了兩種GC模式:Young GC和Mixed GC。

G1的收集過程包含4個步驟:

(1) Initial Marking(初始標記):標記了從GC Root開始直接可達的對象

(2) Concurrent Marking(並發標記):在整個堆上尋找活動對象,標記全部可達對象。這個階段可能會被年輕代垃圾回收中斷。

(3) Remark(重新標記):完成對堆中活動物件的標記。使用一種稱為「快照在開始時」(Snapshot-at-the-Beginning,SATB)的演算法,其速度比CMS收集器中使用的演算法要快得多。

(4) Cleanup(清除垃圾):流程完成3件事情

  • 對活動對象和完全釋放的區域進行記帳。 (Stop The World) 
  • 清理已記住的集合。 (Stop The World) 
  • 重置空的區域並將其返回到空閒清單。 (並發執行)

G1 收集器大致的工作流程如下圖:

啟動 Java進程時,可以透過設定 -XX:+UseG1GC 參數,顯示使用G1回收器。

8.謝南多厄

Shenandoah 也是一款HotSpot 虛擬機回收器,首次出現在Open JDK12中,最初是由RedHat公司開發,2014年貢獻給OpenJDK,或許因為它不是Oracle公司自己開發的,所以,Shenandoah 目前只存在OpenJDK 而不存在OracleJDK商業版中。 Shenandoah主要使用連接矩陣和轉送指標的技術,連接矩陣取代 G1中的卡片表。

Shenandoah工作流程分為9個步驟:

  • Initial Marking(初始標記):和G1 一樣,標記了從GC Root開始直接可達的對象,Stop The World
  • Concurrent Marking(並發標記):和G1 一樣,在整個堆上尋找活動對象,標記全部可達對象。
  • Final Marking(最終標記):和G1 一樣,
  • Concurrent Cleanup(並發清理):清理無存活物件的Region
  • Concurrent Evacuation(並發回收):把存活的物件複製到空的Region中,
  • Inital Update Reference(初始引用更新):修正並發回收階段被複製物件的參考位址
  • Concurrent Update Reference(並發引用更新):引用更新操作
  • Final Update Reference(最終引用更新):修正存在於GCRoots中的引用
  • Concurrent Cleanup(並發清理):回收空的Region
2. Concurrent Marking(并发标记):和G1 一样,在整个堆上查找活动对象,标记全部可达对象。
  • 1.

Shenandoah 收集器大致的工作流程如下圖(圖片來自OpenJDK官方):

啟動 Java進程時,可以透過設定XX:+UseShenandoahGC參數,顯示使用Shenandoah回收器。

注意,如果使用的是Sun/OracleJDK,將無法使用此回收器。

9.中關村 

ZGC 是Oracle官方研發並從JDK11引入,它是一款採用染色指針和讀取屏障技術的回收器,ZGC 和G1一樣,堆空間被劃分成多個Region,不同的是,ZGC的Region 被官方稱為Page,它可以動態建立和銷毀,容量也可以動態調整。

ZGC的Region分為三種:

  • 小型Region:容量固定為2MB,用於存放< 256KB的物件;
  • 中型Region:容量固定為32MB,用於存放>= 256KB且< 4MB的物件;
  • 大型Region:容量為2^n MB,存放>= 4MB 的對象,而且每個大型Region 中只存放一個大對象。由於大物件移動代價過大,所以該物件不會被重新分配。

ZGC 工作流程分為4個步驟:

  • Concurrent Mark(並發標記):和G1 一樣,標記了從GC Root開始直接可達的對象
  • Concurrent Prepare for Relocate(並發預備重分配)
  • Concurrent for Relocate(並發重新分配)
  • Concurrent Remap(並發重映射)

ZGC 收集器大致的工作流程如下圖:

ZGC垃圾回收過程幾乎全部是並發,實際Stop The World(STW)停頓時間極短,不到10ms。這得歸功於其採用的著色指針和讀取屏障技術。

啟動 Java進程時,可以透過設定XX:+UseZGC參數,顯示使用ZGC回收器。

到此,9款垃圾收集器就介紹完畢,如果你對垃圾回收器很感興趣,推薦閱讀周志明博士的《深入理解Java虛擬機》第三版,書中除了垃圾回收器, JVM其它相關的內容也都有詳細介紹,應該是國內很多Java程式設計師學習JVM的必備書籍。

因為篇幅有限,本文只是簡單地分析了HotSpot虛擬機常見的9款垃圾回收器,並沒有做原理上的分析,我會在接下來的文章中分別對CMS,G1,ZGC,Shenandoah 4款垃圾收集器做詳細的講解,連結:JVM專欄。最後用一張圖表對9款回收器做一個比較: