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