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

Java 記憶體模型深度解析:Stack vs Heap 區別與垃圾回收機制全景圖

     編輯:Java 程式設計入門

一、Java 運行時數據區:Stack 與 Heap 核心區別

JVM 將內存劃分為多個區域,其中 Stack(棧/虛擬機棧) 與 Heap(堆) 是開發者最常打交道的兩大核心區域。

維度Stack (虛擬機棧)Heap (堆)
存儲內容基本類型變量、對象引用、方法調用幀、局部變量表、操作數棧所有 new 出來的對象實例、數組、String 常量池(JDK7+)
線程私有性線程私有,生命週期與線程同步線程共享,全局唯一
分配方式棧幀隨方法調用壓棧、返回出棧,自動釋放對象在堆分配,由 GC 回收
訪問速度極快(指針移動)較慢(需 GC 管理、可能觸發 Young/Full GC)
溢出異常StackOverflowError(遞歸過深、大局部變量)OutOfMemoryError: Java heap space
典型大小-Xss 設置(默認 1MB)-Xms/-Xmx 設置(默認物理記憶體 1/64 ~ 1/4)

代碼示例:內存分配圖解


public class MemoryDemo {
    public static void main(String[] args) {          // 1. main 棧幀入棧
        int a = 10;                                   // 2. 基本類型直接在棧分配
        Person p = new Person("Alice", 25);           // 3. 引用 p 在棧,對象在堆
        p.sayHello();                                 // 4. 調用方法,新棧幀壓棧
    }
}

class Person {
    String name;  // 引用類型,指向堆中 String 對象
    int age;      // 基本類型,若為實例字段則在堆對象內部

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    void sayHello() {
        String msg = "Hello " + name; // msg 引用在棧,字符串對象在堆
        System.out.println(msg);
    }
}

關鍵點:Person p 是棧上的引用變量(4/8 字節指針),new Person() 創建的對象實體在堆上。方法參數傳遞的是引用的副本(值傳遞)。

二、垃圾回收機制全景解析

1. 核心目標

自動釋放不再被引用的堆對象佔用的內存,防止 OOM,提高內存利用率。

2. 判斷對象「已死」的算法

可達性分析算法(主流)

  • 從 GC Roots 出發,向下搜索引用鏈
  • 無法到達的對象 = 不可達 = 可回收
  • GC Roots 包括:
    • 虛擬機棧中引用的對象(局部變量表)
    • 方法區類靜態屬性引用的對象
    • 方法區常量引用的對象
    • 本地方法棧 JNI 引用的對象
    • 活躍線程、同步鎖持有的對象等

引用計數法(已淘汰)

無法解決循環引用問題,JDK 主流實現未採用。

3. 四大引用類型與 GC 關係

引用類型強度GC 行為典型場景
強引用最強永不回收(寧可 OOM)Object obj = new Object()
軟引用次強內存不足時回收緩存
弱引用較弱下次 GC 必回收WeakHashMap、監聽器
虛引用最弱無法單獨獲取對象,配合 ReferenceQueue 跟蹤回收DirectBuffer 清理

4. 經典 GC 算法

  • 標記-清除:兩階段,產生碎片
  • 標記-整理:移動對象消除碎片,需更新引用
  • 複製算法:將內存劃分兩塊,存活對象複製到另一塊,無碎片但浪費 50% 空間
  • 分代收集(現代 JVM 標配):新生代用複製,老年代用標記-整理/標記-清除

5. HotSpot 分代收集器演進


串行:Serial (新生代複製) + Serial Old (老年代標記-整理)          -> 單核 Client 模式
並行:Parallel Scavenge + Parallel Old                                -> 吞吐量優先,多核 Server
並發:CMS (老年代並發標記-清除) + ParNew (新生代並行複製)             -> 低延遲,但碎片化、CPU 敏感
區域化:G1 (Region-based, 混合收集)                                   -> 大堆、可預測停頓,JDK9+ 默認
新一代:ZGC / Shenandoah (彩色指針/讀屏障, 亞毫秒級停頓)              -> TB 級大堆,超低延遲

6. 對象晋升規則

  1. 優先在 Eden 分配
  2. 大對象直接進老年代(-XX:PretenureSizeThreshold)
  3. 長期存活對象進老年代(年齡計數器 -XX:MaxTenuringThreshold,默認 15)
  4. 動態年齡判斷:Survivor 同齡對象大小總和 > Survivor 一半,直接晋升
  5. 空間擔保:Minor GC 前檢查老年代連續空間是否足夠

三、開發者輔助 GC 的最佳實踐

  1. 及時置 null:作用域結束前手動 obj = null,縮短強引用鏈(特別是長生命周期集合、緩存)
  2. 避免大對象/大數組頻繁分配:複用對象池、ByteBuffer、StringBuilder
  3. 合理設置 JVM 參數:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:+UseG1GC 等
  4. 監控 GC 日誌:-Xlog:gc*:file=gc.log:time,uptime,level,tags(JDK9+)
  5. 使用弱引用實現緩存:WeakHashMap、Guava Cache、Caffeine

四、常見誤區澄清

誤區 1:基本類型都在棧上。
✅ 修正:局部變量基本類型在棧;實例字段基本類型隨對象在堆;static 基本類型在方法區。

誤區 2:String 常量池在堆。
✅ 修正:JDK 7 前在永久代(方法區),JDK 7+ 移至堆。

誤區 3:System.gc() 能保證立即回收。
✅ 修正:只是建議,JVM 可忽略。

總結

理解 Stack 存引用/局部變量、Heap 存對象實體 是分析內存問題的基石;掌握 分代收集、可達性分析、引用類型、主流收集器特點 能指導 JVM 調優與排查 OOM。實戰中:代碼層面減少強引用持有時間,配置層面選對收集器並壓測驗證,才是高效內存管理之道。

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