首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 垃圾回收機制深度解析:從引用計數到分代收集的演進邏輯

     編輯:Java 相關知識

前言:誰在替你打掃記憶體?

C++ 程序員需手動 delete,稍有疏忽即導致記憶體洩漏;Java 程序員則將此重擔交給 JVM 垃圾收集器 (Garbage Collector, GC)。本文系統梳理 GC 的核心判定算法、觸發時機、代碼干預手段、finalize 陷阱,助你理解記憶體管理的底層邏輯,寫出更友善於 GC 的代碼。

一、對象何時成為「垃圾」?── 可達性分析

Java 採用 可達性分析 而非單純引用計數 (無法解決循環引用):

  • GC Roots:執行緒棧局部變數、方法區靜態屬性/常量、本地方法棧 JNI 引用、同步鎖持有物件等。
  • 判定規則:從 GC Roots 出發向下搜尋,無法到達的物件即為可回收垃圾。
「引用計數」僅作為理解輔助:一個物件無任何引用變數指向時,確實不可達。但循環引用 (A→B→A) 導致計數永不歸零,故生產級 GC 全面採用可達性分析。

二、四種引用強度:精細控制生命週期

引用類型強度回收時機典型場景
強引用最強永不回收 (OOM 前)new Object() 一般物件
軟引用 SoftReference次強記憶體不足前快取、圖片緩存
弱引用 WeakReference較弱下次 GC 時WeakHashMap、監聽器弱引用
虛引用 PhantomReference最弱隨時 (配合 ReferenceQueue)直接記憶體釋放追蹤、精確 finalize 替代

三、垃圾收集算法演進

  1. 標記-清除:標記可達,清除不可達。缺點:碎片化、效率低。
  2. 標記-整理:清除後移動物件消除碎片。適用於老年代。
  3. 複製:將存活物件複製至另一區域。適用於新生代 (Eden + Survivor),無碎片但浪費 50% 空間。
  4. 分代收集 (現代 GC 核心):新生代 (Young Gen) 用複製,老年代 (Old Gen) 用標記-整理,元空間 存類元數據。

四、HotSpot 主流收集器演進時間軸

收集器區域算法特點適用場景
Serial新/老複製/整理單執行緒、Stop-The-WorldClient 模式、小堆
ParNew新複製Serial 多執行緒版配合 CMS
Parallel Scavenge新複製吞吐量優先、自適應調節批次處理、科學計算
CMS老標記-清除低延遲、併發標記/清除Web 服務、響應敏感
G1 (JDK 7u4+)全堆分區標記-整理可預測停頓、區域化、混合收集大堆 (6G+)、綜合場景
ZGC (JDK 11+)全堆染色指標、併發整理亞毫秒停頓、TB 級堆超大堆、低延遲要求
Shenandoah (JDK 12+)全堆併發整理、轉發指標併發整理、停頓與堆大小無關大堆、低延遲

五、代碼層面的干預與最佳實踐

1. System.gc() ── 只是建議

System.gc(); // 僅通知 JVM「適合 GC 了」,不保證立即執行、不保證 Full GC
生產環境極少主動調用,依賴 JVM 人因工程調優 (參數 -XX:+UseG1GC -Xmx4g -Xms4g -XX:MaxGCPauseMillis=200 等)。

2. finalize() ── 別依賴它

  • 執行時機不可預測:物件不可達 → 進入 Finalizer 隊列 → 專門執行緒執行 → 下次 GC 才真正回收。
  • 異常會導致 finalize 停止,物件可能永不被回收。
  • JDK 9 標記 @Deprecated,推薦 try-with-resources / AutoCloseable / Cleaner (JDK 9+) 進行確定性資源釋放。

3. 寫出 GC 友善代碼

  • 縮短物件作用域:局部變數優於欄位,讓物件儘快在 Young Gen 死亡 (Morning GC 極快)。
  • 避免大物件直接進老年代:大陣列、長字串可拆分或使用堆外記憶體 (ByteBuffer.allocateDirect)。
  • 慎用 finalize/PhantomReference:資源釋放用 try-with-resources。
  • 弱引用緩存:WeakHashMap、Guava Cache 自動清理。
  • 關注分配速率:高分配率觸發頻繁 Young GC,優化熱點路徑物件創建 (對象池、StringBuilder 復用)。

六、常見誤區澄清

誤區事實
GC 只回收沒用的物件GC 回收不可達物件;「沒用」是業務邏輯概念,GC 不懂業務。
調用 System.gc() 立即釋放記憶體僅建議,JVM 可能忽略;且 Full GC 耗時長,反而可能加劇停頓。
finalize 類似 C++ 解構函數解構函數確定性執行;finalize 非確定性、單執行緒、易丟失異常。
弱引用物件下次 GC 必回收僅當僅有弱引用指向時;若同時有強引用則不回收。
設 null 就能立即回收斷開強引用僅使物件符合回收資格,實際回收時間由 GC 決定。

結語

Java GC 經歷了從單執行緒 Serial 到分代 Parallel/CMS,再到分區 G1、併發整理 ZGC/Shenandoah 的演進,核心目標始終是:在有限硬體資源下,以最小應用停頓換取最大吞吐量。作為開發者,無需微操 GC,但必須理解可達性判定、分代假設、引用強度、收集器特性,並在代碼層面踐行「朝生夕死、縮短作用域、確定性釋放資源」原則,才能與 GC 協作共舞,構建高效穩定的 Java 應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved