九款常見的 JVM 垃圾回收器
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
Serial 收集器,見名知意,它是一個單線程的收集器,而且在進行垃圾回收時還必須暫停其它的工作線程,直到它收集結束(Stop The World)。
在 JDK 1.3.1 之前,它是 HotSpot虛擬機年輕代收集器的唯一選擇。
Serial(年輕代) 和 Serial Old(老年代) 組合模式下,收集器大致的工作流程如下圖:
盡管 Serial 收集器是單線程回收,并且會暫停其它的工作線程,看起來性能很差,但是,它依然是 HotSpot 虛擬機運行在客戶端模式下的默認新生代收集器,因為相對于其它收集器的單線程,Serial 收集器消耗的內存最低,加上沒有多線程交互的開銷,反而使得它簡單高效。
在啟動 Java進程時,可以通過設置 -XX:+UseSerialGC -XX:+UseSerialOldGC 參數,使用上述回收器組合。
2.ParNew
ParNew 收集器是 Serial 收集器的多線程并行版本,除了使用多線程進行垃圾回收之外,其它的行為和 Serial 收集器都是相同的。主要應用在 HotSpot虛擬機運行在服務端模式下的場景。
ParNew(年輕代) 和 Serial Old(老年代) 組合模式下,收集器大致的工作流程如下圖:
在啟動 Java進程時,可以通過設置 -XX:+UseParNewGC -XX:+UseSerialOldGC 參數,使用上述回收器組合。
3.Parallel Scavenge
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 Old 收集器是 Serial 的老年代版本,它也是一個單線程收集器,使用‘標記-整理’算法,和 Serial 收集器一樣也是用于 HotSpot客戶端模式。
Serial(年輕代) 和 Serial Old(老年代) 組合模式下,收集器大致的工作流程如下圖:
在啟動 Java進程時,可以通過設置 -XX:+UseSerialGC -XX:+UseSerialOldGC 參數,使用上述回收器組合。
5.Parallel Old
Parallel Old 收集器是從 JDK 6 開始提供支持的,它是 Parallel Scavenge 收集器的老年代版本,支持多線程并發收集,采用‘標記-整理’算法。Parallel Old 收集器的出現,真正意義上實現了“吞吐量優先”的目標。
Parallel Scavenge(年輕代) 和 Parallel Old(老年代) 組合模式下,收集器大致的工作流程如下圖:
在啟動 Java進程時,可以通過設置 -XX:+UseParallelGC -XX:+UseParallelOldGC 參數,使用上述回收器組合。
6.CMS
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
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 一樣,在整個堆上查找活動對象,標記全部可達對象。
Shenandoah 收集器大致的工作流程如下圖(圖片來自 OpenJDK官方):
在啟動 Java進程時,可以通過設置XX:+UseShenandoahGC參數,顯示使用 Shenandoah回收器。
注意,如果使用的是Sun/OracleJDK,將無法使用該回收器。
9.ZGC
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款回收器做一個對比: