JVM 垃圾回收算法有哪些?CMS、G1、ParNew、Serial、Parallel 原理是什么?
在 Java 的世界里,垃圾回收(Garbage Collection,GC)就像一位默默奉獻的清潔工,負責清理那些不再被需要的對象。
沒有 GC,我們的程序內(nèi)存遲早會被耗盡。但 GC 的運作并非無代價,不合適的垃圾回收策略可能導致高延遲甚至性能瓶頸。
本章將帶你深入理解 JVM 的垃圾回收機制,從垃圾回收算法到收集器設計,再到 GC 日志分析與調(diào)優(yōu)實踐。
通過對這些內(nèi)容的掌握,您將能在高并發(fā)場景下為 JVM 選擇最佳的垃圾回收管理方案。
垃圾回收重點關注的區(qū)域
在 Java 虛擬機中,垃圾回收機制主要關注于運行時數(shù)據(jù)區(qū)的 "堆空間" 中的數(shù)據(jù),其次關注的是 "方法區(qū)" 中的數(shù)據(jù)。
從垃圾回收頻率上講,新生代、老年代、字符串常量池 和 元空間 都是垃圾回收的重點關注區(qū)域。
圖片
垃圾回收算法
垃圾回收算法是垃圾收集器的基礎。JVM 中經(jīng)典的算法包括標記-清除、復制和標記-整理,它們共同構成了分代垃圾回收策略的理論基礎。
標記-清除算法(Mark-Sweep)
算法原理
標記-清除是最早的垃圾回收算法之一,主要分為兩個階段:
- 標記階段:從 GC Roots 出發(fā),遍歷所有可達對象并將其標記為存活對象。
- 清除階段:回收未被標記的對象,釋放其占用的內(nèi)存。
圖片
優(yōu)點:實現(xiàn)簡單,適合存活對象少的場景。
缺點:內(nèi)存碎片化:未被回收的對象分布不連續(xù),可能導致大對象無法分配;性能低下,需要完整遍歷堆,耗時較長。
標記-清除算法通常用于老年代的垃圾回收,適合生命周期較長、存活率高的對象。
復制算法(Copying)
復制算法將堆分為兩塊相等的內(nèi)存區(qū)域(From 和 To)。每次 GC 時,只掃描 From 區(qū),將存活對象復制到 To 區(qū),最后清空 From 區(qū)。
圖片
優(yōu)點:沒有碎片化問題,內(nèi)存分配效率高。
缺點:浪費內(nèi)存:需要額外空間保存對象;不適合老年代,存活對象多時復制成本高。
復制算法通常用于新生代回收,新生代對象存活率低,復制成本較低。
標記-整理算法(Mark-Compact)
標記-整理(Mark-Compact)是標記-清除的改進版,標記階段相同,但清除階段會將存活對象移動到內(nèi)存的一端,然后清理剩余空間。
圖片
優(yōu)點:消除了內(nèi)存碎片。不需要額外空間。
缺點:對象移動成本較高。
標記-整理算法通常用于老年代回收,解決老年代內(nèi)存碎片問題。
分代收集算法
上面所介紹的 標記清除算法、復制算法 和 標記整理算法,它們各自都具有自己獨特的優(yōu)勢和特點,每種算法都有各自相適應的場景,沒有一種算法可以完全替代另一種算法。
在這樣的背景下 分代收集算法 孕育而生,由于每個對象的生命周期各不相同,有的對象可以長期存活,有的對象朝生夕滅。
因此,針對不同生命周期的對象,可以采取不同的回收方式來提高回收效率。
在一般情況下,市面上大多數(shù) Java 虛擬機中的 GC 都采用分代收集,即將 堆空間 劃分為 新生代 和 老年代,不同生命周期的對象放到不同的區(qū)域中進行存儲,并且針對不同的區(qū)域采用不同的回收策略,這就是我們常說的 分代收集 (Generational Collection)。
垃圾收集器深入解析
JVM 中的垃圾收集器基于上述算法設計,針對不同場景優(yōu)化性能。接下來,我們深入剖析常見收集器的實現(xiàn)原理和應用場景。
Serial 收集器
Serial 收集器是單線程收集器,采用復制算法回收新生代,標記-整理算法回收老年代。GC 時會 Stop-The-World(STW),暫停所有應用線程。
圖片
特點
- 實現(xiàn)簡單,單線程操作效率高。。
- 使用 復制算法 回收新生代,標記-整理算法 回收老年代。
- 沒有線程切換的開銷。
- GC 時應用完全暫停,延遲較高。
適用于單線程應用或內(nèi)存占用較小的場景,如嵌入式設備和簡單的命令行工具。
-XX:+UseSerialGC: 啟用 Serial 新生代和 Serial Old 老年代回收器。
ParNew 回收器
arNew GC 是一款并行回收器,除了在進行垃圾回收時使用多線程并發(fā)執(zhí)行外,其它方面幾乎和 Serial GC 一致,包括 回收算法、Stop The World、對象分配規(guī)則 和 回收策略 等,因此常稱 ParNew GC 為 Serial GC 的多線程版本。
不過 ParNew GC 屬于新生代回收器,只能對新生代中的對象進行回收。
圖片
◆ 優(yōu)點:
① 支持并行回收: ParNew GC 支持多線程并行進行回收,可以利用多核 CPU 的優(yōu)勢,提高垃圾回收的效率;
② 回收效率高且停頓時間短: ParNew GC 是一個專門用于回收年輕代的垃圾垃圾回收器,使用的是復制算法,并且回收的空間比價小,所以回收效率高且停頓時間短;
◆ 缺點:
① 浪費部分內(nèi)存空間: ParNew GC 使用的是復制算法,需要將內(nèi)存空間拆成兩份,每次只使用其中一份內(nèi)存空間存儲對象,所以比較浪費內(nèi)存空間;
② 老年代垃圾回收效率低: 由于 ParNew GC 只用于年輕代垃圾回收,而不處理老年代垃圾回收,因此老年代的垃圾回收效率低下,容易導致 Full GC;
◆ ParNew 回收器常配置參數(shù)。
① -XX:+UseParNewGC: 啟用新生代回收器 ParNew。
② -XX:ParallelGCThreads: 配置 GC 的線程數(shù)量,通常推薦該值和 CPU 核心數(shù)量保持一致。
Parallel 收集器
Parallel 收集器使用多線程并行回收新生代(復制算法)和老年代(標記-整理算法),以提高吞吐量。
圖片
特點:
- 多線程并行回收,適合多核 CPU。
- 優(yōu)化吞吐量,回收時應用暫停時間較長。
- STW 時間較長,對低延遲場景不友好。
適用于吞吐量優(yōu)先的后臺任務,如大數(shù)據(jù)處理和批量計算。
Parallel 回收器常配置參數(shù):
- -XX:+UseParallelGC: 啟用新生代回收器 Parallel Scavenge。
- -XX:+UseParallelOldGC: 啟用老年代回收器 Parallel Old。
- -XX:ParallelGCThreads: 配置 GC 的線程數(shù)量,通常推薦該值和 CPU 核心數(shù)量保持一致。
- -XX:MaxGCPauseMillis: 配置 GC 回收最大停頓時間,即 Stop The World 時間。
- -XX:GCTimeRatio:配置 GC 回收時間占總時間的比例,用于衡量吞吐量的大小,取值范圍為 0-100,默認值為 99,也就表示垃圾回收時間不超過 1%。
該參數(shù)與 -XX:MaxGCPauseMillis 參數(shù)有一定矛盾性,因為設置較短的停頓時間可能會導致更多的 GC 次數(shù),從而增加總的 GC 時間,使得 GCTimeRatio 容易超過設定的比例。
- -XX:+UseAdaptiveSizePolicy:配置 Parallel Scavenge 回收器具有自適應調(diào)節(jié)策略。
- 在這種模式下,新生代 Eden 和 Survivor 的比例、晉升老年代的對象年齡等參數(shù)會被自動調(diào)整,期望在堆大小、吞吐量和停頓時間之間達到最佳平衡。
- 在手動調(diào)優(yōu)比較困難的場合,可以直接使用這種自適應的方式,僅指定虛擬機的最大堆、目標的吞吐量 (GCTimeRatio) 和停頓時間 (MaxGCPauseMillis),讓虛擬機自己完成調(diào)優(yōu)工作。
CMS 收集器
CMS 收集器使用并發(fā)回收方式,降低 STW 時間,這款回收器是 HotSpot 虛擬機中第一款真正意義上的并發(fā)回收器,它第一次實現(xiàn)了讓垃圾收集線程與用戶線程同時工作。
CMS GC 是一款老年代回收器,其主要回收目標就是老年代中的對象,使用的是標記清除算法。
老年代回收分為四個階段:
- 初始標記:標記 GC Roots 可達的對象(STW)。
- 并發(fā)標記:并發(fā)掃描對象引用關系(無 STW)。
- 重新標記:處理并發(fā)期間新增的對象(STW)。
- 并發(fā)清除:清理無效對象(無 STW)。
圖片
特點:
- 并發(fā)回收,減少暫停時間。
- 存在內(nèi)存碎片化問題。
- 并發(fā)時可能影響應用性能。
適用于響應時間敏感的場景,如 Web 應用和在線交易系統(tǒng)。
CMS 回收器可配置參數(shù):
- -XX:+UseConMarkSweepGC: 啟用 CMS 老年代回收器。
- -XX:ParallelcMSThreads: 設置 CMS GC 執(zhí)行時的線程數(shù)量。
- -XX:CMSInitiatingOccupanyFraction: 設置堆內(nèi)存使用率的閾值,一旦達到該閾值,便開始進行回收。
- -XX:+UseCMSCompactAtFullCollection: 設置是否在執(zhí)行完 Full GC 前對內(nèi)存空間進行壓縮整理,以此避免內(nèi)存碎片的產(chǎn)生。
- -XX:CMSFullGCsBeforeCompaction: 設置在執(zhí)行多少次 Full GC 后對內(nèi)存空間進行壓縮整理。
G1 收集器
G1 將堆劃分為多個固定大小的 Region,每個 Region 都是連續(xù)的一段內(nèi)存區(qū)域,大小在 1MB ~ 32MB 之間,且為 2 的 N 次冪,具體大小根據(jù)堆的實際大小而定。
并且每個 Region 扮演著不同的角色,比如可以是 Eden 區(qū)、Survivor 區(qū)、Old 區(qū)或 Humongous 區(qū)等,通過全局標記統(tǒng)計每個 Region 的垃圾量,優(yōu)先回收垃圾最多的 Region,降低停頓時間。
圖片
G1 回收器整體使用的是 標記-整理算法,局部使用的是 復制算法。
在 G1 中支持三種回收模式,分別為 Young GC、Mixed GC 和 Full GC:
- Young GC: 對新生代中的 Region 進行回收。當新生代的空間不足時會觸發(fā) Young GC,回收新生代中的垃圾對象。
- Mixed GC: 這是一種混合回收模式,不僅回收新生代中的 Region,還會回收部分老年代中的 Region。這種模式旨在減少 Full GC 的頻率,通過定期回收部分老年代 Region 來降低整個堆的垃圾積累。
- Full GC: 對整個堆空間的所有 Region 進行回收,包括新生代和老年代。Full GC 通常在 G1 無法通過 Mixed GC 清理足夠的空間時觸發(fā),或者在某些特殊情況下觸發(fā) (如系調(diào)用 system.gc() 方法觸發(fā))。
G1 回收器優(yōu)缺點
◆ 優(yōu)點:
可預測的停頓時間: G1 GC 將堆空間分成多個大小相等的 Region (區(qū)域),這些 Region 可以作為 Eden 區(qū)、Survivor 區(qū)或 Old 區(qū)等。通過在多個 Region 中執(zhí)行并發(fā)垃圾收集,G1 能夠實現(xiàn)可預測的停頓時間,這意味著垃圾收集的停頓時間可以預測和控制;
高效的內(nèi)存回收: G1 GC 使用增量式的垃圾收集算法,不僅在特定區(qū)域中進行垃圾回收,還在整個堆中進行。這種全局性的垃圾回收方式使得 G1 能夠高效地回收內(nèi)存,并減少垃圾收集的停頓時間;
優(yōu)化的內(nèi)存分配: G1 GC 使用了一種名為 Remembered Set 的數(shù)據(jù)結構,可以追蹤對象之間的引用,從而在內(nèi)存分配時避免掃描整個堆。這一特性可以顯著減少內(nèi)存分配的時間;
空間整理: G1 GC 通過對未使用的 Region 進行空間整理,將零散的空閑空間合并成更大的連續(xù)空間塊,從而優(yōu)化內(nèi)存使用,提高堆的利用率;
◆ 缺點:
對硬件資源要求較高: G1 GC 需要在多核 CPU 和大內(nèi)存環(huán)境下運行才能充分發(fā)揮其優(yōu)勢。如果在較小的硬件資源上運行,可能會導致運行緩慢或 OutOfMemoryError 等問題;
初始標記和最終標記的時間較長: G1 GC 的初始標記和最終標記階段需要單線程執(zhí)行,并且會暫停應用程序。在處理大型內(nèi)存空間時,這兩個階段可能會導致較長的停頓時間;
混合收集過程可能會影響吞吐量: G1 GC 的混合收集過程涉及多次暫停應用程序,這可能會對應用程序的吞吐量產(chǎn)生一定的影響;