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

JVM 內存模型與垃圾回收全景解析:從運行時區域到 GC 算法演進

     編輯:Java 綜合教學

JVM 內存模型與垃圾回收全景解析:從運行時區域到 GC 算法演進

深入理解 JVM 內存結構是排查 OOM、優化 GC、調優性能的基石。本文系統梳理運行時數據區域、對象存活判定算法、引用類型體系、四大經典 GC 算法及分代收集策略。

一、JVM 運行時數據區域(JDK 1.8+)

JVM內存結構圖

區域 線程共享性 核心功能 異常類型
程序計數器 私有 當前線程執行字節碼行號指示器 無 OOM
虛擬機棧 私有 存儲棧幀(局部變量表、操作數棧、動態鏈接、方法出口) StackOverflowError / OOM
本地方法棧 私有 服務 Native 方法 StackOverflowError / OOM
Java 堆 共享 對象實例分配主戰場,GC 主管理區域 OutOfMemoryError
方法區 共享 類信息、常量、靜態變量、JIT 編譯代碼 OutOfMemoryError
運行時常量池 共享 方法區一部分,存編譯期字面量/符號引用,String.intern() 動態擴展 OutOfMemoryError

註:JDK 1.8 移除永久代,元空間使用本地內存;字符串常量池移至堆中。

二、對象存活判定:從引用計數到可達性分析

1. 引用計數法

  • 原理:對象頭維護計數器,引用+1,失效-1,歸零即可回收。
  • 缺陷:無法解決循環引用(A→B, B→A 計數永不為 0)。
  • 結論:主流 JVM(HotSpot)未採用。

2. 可達性分析算法

可達性分析圖

  • GC Roots 為起點,向下搜索引用鏈,不可達對象標記為可回收。
  • GC Roots 包括:
    • 虛擬機棧局部變量表引用的對象
    • 方法區類靜態屬性/常量引用的對象
    • 本地方法棧 JNI 引用的對象
  • 循環引用對象若無 GC Roots 連接,依然可被回收。

3. 對象「臨死前」的自救:finalize()

  • 不可達對象首次標記後,篩選是否需執行 finalize()(未覆寫或已執行過視為不需執行)。
  • 第二次標記確認仍不可達,才真正宣告死亡。
  • 自救機會僅一次:finalize() 最多被自動調用一次。
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        SAVE_HOOK = this; // 自救:重新建立 GC Roots 引用
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        SAVE_HOOK = null;
        System.gc(); Thread.sleep(500);
        System.out.println(SAVE_HOOK != null ? "存活" : "死亡"); // 存活

        SAVE_HOOK = null;
        System.gc(); Thread.sleep(500);
        System.out.println(SAVE_HOOK != null ? "存活" : "死亡"); // 死亡(二次不再調用 finalize)
    }
}

三、四種引用類型與應用場景

引用類型 強度 回收時機 典型用途 API
強引用 最強 永不回收 常規對象 Object o = new Object() —
軟引用 次強 內存不足前(二次回收) 緩存(圖片、網頁內容) SoftReference
弱引用 較弱 下次 GC 時 監聽器、回調、ThreadLocalMap WeakReference
虛引用 最弱 被回收時收到通知 直接內存釋放追蹤、清理動作 PhantomReference
// 軟引用緩存示例
SoftReference<Bitmap> cache = new SoftReference<>(loadBitmap());
Bitmap bmp = cache.get();
if (bmp == null) { // 已被回收
    bmp = loadBitmap();
    cache = new SoftReference<>(bmp);
}

四、經典 GC 算法演進

  • 實現簡單
  • 內存碎片多
  • 分配效率低
  • 無碎片
  • 分配指針碰撞快
  • 內存利用率 50%
  • 存活多時效率低
  • 無碎片
  • 內存利用率高
  • 移動對象開銷大
  • 綜合上述優點
  • 符合對象「朝生夕死」特性
  • 實現複雜
  • 算法 核心思想 優點 缺點 適用區域
    標記-清除 標記存活,清除死亡 老年代(配合整理)
    復制 劃分兩塊,存活對象復制到另一塊 新生代
    標記-整理 標記後向一端移動存活對象,清理邊界外 老年代
    分代收集 按對象壽命劃分新生代/老年代,不同代用不同算法 整個堆(現代 JVM 主流)

    五、分代收集具體佈局與流程

    • 新生代:Eden + S0 + S1(默認 8:1:1),Minor GC 觸發復制算法,存活對象在 Survivor 間輪換,年齡達閾值(默認 15)晉升老年代。
    • 老年代:Major/Full GC 觸發標記-整理,大對象直接進老年代(-XX:PretenureSizeThreshold)。
    • 動態年齡判定:Survivor 同齡對象總大小超過 Survivor 一半,直接晉升。
    • 空間擔保:Minor GC 前檢查老年代最大可用連續空間是否大於新生代所有對象總大小,否則觸發 Full GC。

    六、參考資源

    1. 《深入理解 Java 虛擬機》第 2、3 章
    2. JVM 規範:運行時數據區域、垃圾回收章節
    3. HotSpot 源碼:gc/ 目錄下各收集器實現
    1. 上一篇文章:
    2. 下一篇文章:
    Copyright © 程式碼之家 All Rights Reserved