前言:
我們知道在堆內存中,會有自動的垃圾回收功能,那今天這篇文章將會向你介紹,這個功能實現的方式,還有實現的對象,接下來就由我來給你們詳細介紹垃圾回收的算法和實現算法的回收器。
目錄
前言:
常見的四種垃圾回收算法
標記清除算法
核心思想
實現過程兩個階段
使用場景
標記整理算法
核心思想
實現過程
使用場景
復制算法
核心思想
實現過程
?使用場景
分代gc算法
核心思想
Minor GC 前的決策流程
?關鍵步驟詳解
檢查1:老年代空間 > 新生代總大小
檢查2:老年代空間 > 歷史平均晉升大小
檢查失敗:直接觸發 Full GC
常見垃圾回收器
年輕代垃圾回收器(Minor GC)
1. Serial 收集器
2. ParNew 收集器
3. Parallel Scavenge 收集器
老年代垃圾回收器(Major GC/Full GC)
1. Serial Old 收集器
2. Parallel Old 收集器
3. CMS(Concurrent Mark-Sweep)收集器
G1垃圾回收器
G1 的垃圾回收過程
主要優點
主要缺點
不適用場景
總結
常見的四種垃圾回收算法
標記清除算法
核心思想
通過?可達性分析?標記存活對象,直接回收未標記對象的內存
實現過程兩個階段
-
標記階段(STW):
-
從 GC Roots(棧引用、靜態變量等)出發,遞歸遍歷對象圖。
-
對存活對象打標記。
-
-
清除階段(STW):
-
線性掃描堆內存。
-
回收未標記對象的內存塊(加入空閑鏈表)。
-
優點:實現簡單,第一階段標記,第二階段清除。
缺點:
- 碎片化內存:內存是連續的,如果在對象被刪除之后,就會出現很多細小的內存,如果我們需要很大的內存空間,那么就很可能無法匹配。
很明顯,無法做到,因為紅色的就是空閑內存但是最大的才4個字節。
- 分配速度慢:內存碎片化,所以在回收內存之后,會把這段空閑內存加入一個空閑鏈表,每次分配內存都會遍歷整個鏈表找到合適的位置。
使用場景
-
老年代回收:CMS 收集器的回收基礎
-
大對象堆:對象存活率高,移動成本大
-
嵌入式系統:資源受限環境(如 RTOS)
標記整理算法
核心思想
在標記存活對象后,移動對象消除碎片,使空閑內存連續。我們可以把它看作是基于標記清除的一個處理碎片化內存的算法。
實現過程
-
標記階段:
-
從 GC Roots(棧引用、靜態變量等)出發,遞歸遍歷對象圖。
-
對存活對象打標記。
-
-
整理階段(STW):
-
滑動整理:將存活對象“滑動”到內存一端。
-
清理:清理掉沒有存活對象。
-
優點:
- 內存的使用效率高。
- 沒有碎片化內存。
缺點:
- 整理階段效率不高,效率是低于標記清除算法的
使用場景
-
老年代回收:Serial Old, Parallel Old, ZGC
-
低碎片需求:實時系統、長期運行服務
-
內存敏感場景:Android ART 的 Foreground GC
復制算法
核心思想
將內存分為兩等份,只使用其中一份;GC 時將存活對象復制到另一份空間,清空原空間。
實現過程
-
內存劃分:堆分為?From 區(當前使用)和?To 區(空閑),創建對象時都只在From區。
-
垃圾回收階段(STW):
-
從 GC Roots 遍歷存活對象
-
將存活對象復制到 To 區(保持緊湊排列),刪除from區對象
-
交換 From/To 區角色
-
優點:
- 不會發生碎片化
- ?回收高效(僅處理存活對象)
缺點:
- 內存使用率低,每次只使用一半的內存
- 對象復制開銷大
?使用場景
-
年輕代回收:HotSpot 的 Serial/ParNew/Parallel Scavenge
-
短命對象場景:對象存活率 < 10% 時最優
-
小內存區域:JVM 的 Survivor 區
分代gc算法
現代優秀的垃圾回收算法,會將上面的幾種垃圾回收算法組合使用,其中應用最廣的就是分代垃圾回收。
核心思想
基于 對象的存活時間長久將堆劃分為年輕代(活得短),老年代(活得長),對每代應用不同算法。
年輕代:
- 分為Eden區用于存儲新創建的對象。
- 幸存者區:這里方便觀察,分為S0和S1,這個區使用復制算法進行垃圾回收。
實現過程:
-
年輕代(Young Generation)
-
算法:復制算法
-
過程(剛剛創建時):
-
對象分配在 Eden
-
Eden 滿時觸發?Minor GC:
存活對象復制到 Survivor 區中的To區,然后修改交換From與To區
-
-
-
老年代(Old Generation)
-
算法:標記清除?或?標記整理
-
觸發:空間不足時啟動?Full GC
-
詳細過程(第一次創建對象):剛創建的對象會放在Eden區,然后當Eden滿了,新創建的對象已經放不進去了,就會觸發第一次Minor GC清理垃圾,隨后把存活的對象放入,幸存者To區。隨后把From和To區交換。
在接下來的對象就又可以放入Eden區。
隨后Eden區又滿了,這個時候就會再次觸發Minor GC,此時From區和Eden區都會進行垃圾清理,把存活下的對象放入To區(復制算法)。
那老年代中的對象是怎么放進去的呢?
其實在每一次Minor GC后都會使存活下的對象年齡+1,年齡初始值為0,當年齡達到閾值(最大值15,跟垃圾回收器的種類有關。)就會成功晉升到老年代存儲。
進入老年代的條件:
- 當年齡達到閾值
- 當幸存者區中的To區無法容納新的對象時,To區中的對象不管年齡多少都會放入,老年代
其實我們要清楚,每次觸發Minor GC后都有可能會晉升對象到老年代,那如果老年代空間不夠咋辦。下面Minor GC的詳細過程過程。
Minor GC 前的決策流程
?關鍵步驟詳解
檢查1:老年代空間 > 新生代總大小
-
目的:確保老年代能容納最壞情況(整個新生代對象全部存活晉升)
-
通過條件:
Old Free > Eden + Survivor
-
結果:安全執行 Minor GC
檢查2:老年代空間 > 歷史平均晉升大小
-
目的:基于歷史數據預測本次晉升風險
-
通過條件:
Old Free > Avg(Promoted)
-
結果:冒險執行 Minor GC
-
JVM 會嘗試 Minor GC,但已準備好后備方案
-
若晉升時老年代空間不足 →?立即觸發 Full GC
-
檢查失敗:直接觸發 Full GC
-
條件:
Old Free < Avg(Promoted)
-
結果:跳過本次 Minor GC,直接 Full GC
Full GC:同時清理年輕代和老年代
兩次判斷:本質上也是判斷老年代空間還夠不夠
歷史平均晉升大小:把每次晉升的對象大小加起來/個數得到平均大小。
默認情況下年輕代大小遠小于老年代
最后如果觸發了Full Gc之后還是無法存儲我們的新對象,就會OOM。
常見垃圾回收器
垃圾回收器就是垃圾回收算法的具體實現。垃圾回收器分為年輕代和老年代,所以必須成對使用。
年輕代垃圾回收器(Minor GC)
1. Serial 收集器
-
算法:復制算法(單線程)
-
工作方式:
-
觸發 Minor GC 時暫停所有應用線程(Stop-The-World)
-
單線程完成存活對象標記、復制到 Survivor 區/老年代
-
-
適用場景:
-
客戶端模式(如桌面應用)
-
單核服務器或小內存應用(<100MB)
-
-
優點:實現簡單,無線程交互開銷
-
缺點:STW 停頓明顯
2. ParNew 收集器
-
算法:復制算法(多線程并行)
-
工作方式:
-
Serial 的多線程版本,需與 CMS 搭配使用
-
多線程并行執行標記和復制(線程數通過?
-XX:ParallelGCThreads
?控制)
-
-
適用場景:
-
需與 CMS 配合的服務端應用(如 Web 服務)
-
-
優點:縮短年輕代 STW 時間
-
缺點:在單核環境下性能可能不如 Serial
3. Parallel Scavenge 收集器
-
算法:復制算法(多線程并行)
-
核心目標:最大化吞吐量
-
關鍵參數(可以手動配置):
-
-XX:MaxGCPauseMillis
:最大 GC 停頓時間目標(不保證) -
-XX:GCTimeRatio
:吞吐量目標(默認 99%,即 GC 時間占比 ≤1%)
-
-
適用場景:
-
后臺計算、批處理任務(如大數據分析)
-
-
優點:吞吐量優先,自適應調節堆大小
-
缺點:停頓時間不穩定
它是JDK8默認的年輕代垃圾回收器,關注吞吐量,可以自動調節堆內存的大小。
老年代垃圾回收器(Major GC/Full GC)
1. Serial Old 收集器
-
算法:標記-整理算法(單線程)
-
工作方式:
-
單線程 STW 執行標記、整理(壓縮內存消除碎片)
-
-
用途:
-
客戶端模式下的老年代回收
-
作為 CMS 失敗時的后備方案
-
2. Parallel Old 收集器
-
算法:標記-整理算法(多線程并行)
-
工作方式:
-
Parallel Scavenge 的老年代搭檔
-
多線程并行標記和整理
-
-
目標:與 Parallel Scavenge 協同最大化吞吐量
-
適用場景:
-
高吞吐量要求的服務端應用(如數據倉庫)
-
3. CMS(Concurrent Mark-Sweep)收集器
-
算法:標記-清除算法(并發執行)
-
目標:最小化暫停時間
-
執行流程:
-
初始標記(STW):標記 GC Roots 直接關聯的對象(速度快)
-
并發標記(與應用并行):遍歷對象圖
-
重新標記(STW):修正并發標記期間的引用變化
-
并發清除(與應用并行):回收垃圾
-
-
優點:大部分工作并發執行,停頓時間短
-
缺點:
-
內存碎片:需定期 Full GC(Serial Old)整理
-
CPU 敏感:并發階段占用線程資源
-
浮動垃圾:并發清理時新產生的垃圾需下次回收
-
G1垃圾回收器
JDK9之后的默認垃圾回收器,Parallel Scavenge關心吞吐量,CMS關心最大暫停時間,G1的設計就是把他們的優點進行融合。
G1的整個堆被劃分為多個相同大小的區域,稱為Region,區域不要求連續,分為Eden,Survivor,Old區,Region的大小通過堆空間大小/2048的到。
G1 的垃圾回收過程
G1 的回收過程分為以下四個階段,部分階段需要 “Stop The World”(STW):
-
初始標記(Initial Mark)
- STW 階段,標記 GC Roots 直接引用的對象。
- 耗時短,僅需掃描根對象和年輕代的 Region。
-
并發標記(Concurrent Mark)
- 與應用程序并發執行,從 GC Roots 開始遍歷所有可達對象。
- 過程中會記錄對象圖的變化(通過 SATB 算法),避免漏標。
-
重新標記(Remark)
- STW 階段,處理并發標記期間的對象圖變化,確保標記完整性。
- 采用增量更新算法,耗時比 CMS 更短。
-
篩選回收(Cleanup & Evacuation)
- 計算每個 Region 的垃圾占比,根據停頓時間目標選擇價值最高的 Region 進行回收。
- 移動存活對象到新 Region,釋放舊 Region 空間,實現內存整理(無碎片化)。
主要優點
-
可預測的低延遲
- 通過
-XX:MaxGCPauseMillis
參數控制最大停頓時間,優先回收價值高的 Region,避免全堆掃描。 - 相比 CMS 的 "標記 - 清除" 算法,G1 的 "標記 - 整理" 算法避免了內存碎片化,減少 Full GC 頻率。
- 通過
-
分代與分區的結合
- 邏輯上分代(年輕代 / 老年代),物理上分區(Region),靈活性高。
- 大對象直接分配到 Humongous Region,避免頻繁晉升導致的性能損耗。
-
并行與并發處理
- 標記階段與應用線程并發執行,減少 STW 時間。
- 回收階段采用多線程并行處理,提升效率。
-
大內存處理能力
- 對于堆內存超過 4GB 的應用,G1 的分區設計使其管理效率顯著高于 CMS 和 Parallel GC。
主要缺點
-
內存占用開銷
- 為實現精確的停頓控制,G1 需要維護 Remembered Set 和 Card Table 等數據結構,增加約 10%-15% 的內存開銷。
-
算法復雜度高
- 相比 Parallel GC,G1 的算法更復雜,在小內存場景下可能不如傳統回收器高效。
不適用場景
-
小內存應用(堆內存 < 2GB)
- 此時 G1 的內存開銷和算法復雜度可能導致性能不如 Serial 或 Parallel GC。
-
吞吐量優先的批處理應用
- 如數據挖掘、科學計算等,對延遲要求不高,Parallel GC 可能更合適。
-
單線程環境
- G1 的多線程并行優勢無法發揮,Serial GC 可能更輕量。
總結:G1 垃圾回收器是現代 Java 應用的首選 GC 方案,尤其適合大內存、低延遲的場景。其核心價值在于通過分區設計和可預測的停頓控制,平衡了吞吐量和響應時間。但在實際應用中,需根據堆內存大小、處理器資源和業務特性合理配置參數,以達到最優性能。
G1垃圾回收器不管是老年代還是年輕代都可以使用,所以不需要搭配使用。
在JDK9的版本之后默認就是它所以也建議使用它。
Region Size必須是2的指數冪,取值范圍1M-32M
總結
本文介紹了常見的垃圾回收算法和常見的垃圾回收器,對于他們的優點缺點和使用場景做了一定的介紹,希望通過本篇文章,你可以有所收獲。
感謝你的閱讀,創作不易,如果對你有幫助希望點贊收藏加轉發。