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

JVM 垃圾回收機制深度解析:從根遍歷到分代收集

     編輯:Java 相關知識

為什麼需要垃圾回收?

JVM 在堆積區管理所有物件記憶體,不像 C++ 有「自動變量」(棧上分配,離開作用域自動釋放)。Java 所有物件皆在堆上,必須由 GC 自動回收,否則會發生記憶體洩漏導致 OutOfMemoryError。

物件如何被判定為「垃圾」?

引用計數法——已被淘汰

早期嘗試:被引用 +1,引用消失 -1,歸零即可回收。致命缺陷:無法處理循環引用(A 參考 B,B 參考 A),導致計數永不歸零。

根搜尋算法—— 現代標準

從 GC Roots 出發,沿著引用鏈向下遍歷:

  • GC Roots 包含:虛擬機棧區域引用的物件、方法區靜態屬性/常數引用的物件、本地方法棧 JNI 引用的物件、同步鎖持有的物件、類別物件、系統類別載入器等。
  • 可達性分析:若物件到 GC Roots 無任何引用鏈相連,則判定為不可達,標記為可回收。

分代收集理論與堆結構

基於弱分代假說:

  1. 大多數物件朝生夕死(短命)。
  2. 熬過越多次 GC 的物件,越難死(長命)。

堆記憶體劃分

+--------------------------+
|        Heap              |
|  +--------------------+  |
|  |   Young Generation |  |  <- 新生代 (Eden + 2 Survivor)
|  |  +--------------+  |  |
|  |  | Eden         |  |  |  <- 新物件誕生地
|  |  +--------------+  |  |
|  |  | Survivor 0   |  |  |  <- 任一時刻至少一個 Survivor 為空
|  |  | Survivor 1   |  |  |
|  |  +--------------+  |  |
|  +--------------------+  |
|  |   Old Generation   |  |  <- 老年代 (長命物件)
|  +--------------------+  |
|  |   Permanent Gen    |  |  <- JDK 8 前:類別元數據;JDK 8+ 移至 Metaspace (Native Memory)
|  +--------------------+  |
+--------------------------+

物件年齡遷移流程

  1. 新物件優先分配在 Eden。
  2. Eden 滿觸發 Minor GC,存活物件複製到 Survivor 0(From),年齡設為 1。
  3. 下次 Minor GC:Eden + From 存活物件複製到 Survivor 1(To),年齡 +1。From 清空。
  4. 雙 Survivor 輪換,確保始終有一個空閒區供複製。
  5. 物件年齡達到 MaxTenuringThreshold(預設 15,CMS 預設 6) → 晉升至 Old Generation。
  6. Old Generation 滿觸發 Major GC / Full GC(含壓縮整理),耗時較長。

非 Java 物件(本地資源)釋放策略

檔案句柄、Socket、資料庫連線、JNI 記憶體等不受 GC 管理,必須手動釋放。

錯誤做法:依賴 finalize()

// 危險!finalize() 不保證執行、執行時間不定、異常會中斷 finalizer 執行緒
protected void finalize() {
    try { nativeResource.close(); } finally { super.finalize(); }
}

正確做法:Try-with-resources (JDK 7+)

// 編譯器自動生成 finally 區塊關閉資源,即使發生異常也保證釋放
try (Connection conn = dataSource.getConnection();
     PreparedStatement stmt = conn.prepareStatement(sql);
     ResultSet rs = stmt.executeQuery()) {
    // 使用資源
} // 自動關閉 rs, stmt, conn

替代方案:AutoCloseable + 手動管理

public class NativeResource implements AutoCloseable {
    private long nativeHandle;
    public void close() { nativeFree(nativeHandle); } // JNI 呼叫釋放
}

// 使用者必須負責調用 close()
NativeResource res = new NativeResource();
try {
    // use
} finally {
    res.close();
}

關於 System.gc() 的迷思

  • 它只是建議 JVM 執行 GC,不保證 立即執行。
  • GC 由低優先級執行緒運行,通常在記憶體不足導致分配失敗、或執行緒因等待記憶體而阻塞時才真正觸發。
  • 生產環境禁止 手動呼叫 System.gc(),應透過 JVM 參數調優(如 -Xms, -Xmx, -XX:+UseG1GC 等)。

現代 GC 收集器選型指南 (JDK 11+)

收集器適用場景特點
G1 GC大多數通用應用 (預設)區域化、可預測停頓、混合收集、支援超大堆
ZGC超低延遲、超大堆 (TB 級)次世代、並發整理、停頓 < 1ms、JDK 11+ 實驗/15+ 正式
Shenandoah低延遲、大堆並發整理、紅綠指標、OpenJDK 專案、JDK 12+
Serial GC單核、小堆、嵌入式單執行緒、Stop-the-world、開銷最小

理解 GC 核心原理(根搜尋、分代、安全點、記憶體屏障),比背誦參數更重要。遇到 OOM 時,請先用 jmap -histo:live、jhat、VisualVM、Arthas、JProfiler 分析堆轉儲檔,找出真正佔用記憶體的物件與引用鏈。

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