Java 程序運行時數據存儲區域詳解:堆、棧、方法區、常量池
Java 虛擬機在運行時將內存劃分為若干區域,每個區域有特定用途與生命週期。理解這些區域對排查 OOM、優化性能、理解對象生命週期至關重要。
運行時數據區域概覽(JDK 8+)
| 區域 | 線程共享性 | 主要內容 | 異常 |
| 堆 | 共享 | 幾乎所有對象實例、數組 | OutOfMemoryError: Java heap space |
| 虛擬機棧 | 私有 | 棧幀(局部變量表、操作數棧、動態鏈接、返回地址) | StackOverflowError / OOM |
| 本地方法棧 | 私有 | Native 方法調用 | StackOverflowError / OOM |
| 方法區 | 共享 | 類信息、常量、靜態變量、即時編譯代碼 | OOM: Metaspace |
| 運行時常量池 | 共享 | 類文件常量池內容、符號引用、字符串常量 | OOM |
| 程序計數器 | 私有 | 當前執行字節碼行號 | 無 |
重點區域深度解析
1. 堆 - 對象實例的主戰場
- 所有
new 的對象、數組在堆分配
- GC 主要工作區域(分代:新生代 Eden/S0/S1、老年代)
- -Xms/-Xmx 控制大小
2. 虛擬機棧 - 方法執行的內存模型
- 每個方法調用創建一棧幀,存儲:局部變量表(基本類型、引用)、操作數棧、動態鏈接、返回地址
- 局部變量表以 Slot 為單位,long/double 佔 2 Slot
- -Xss 控制棧大小,深度遞歸易致 StackOverflowError
3. 方法區 / Metaspace - 類元數據存儲
- JDK 8 移除永久代,改用本地內存實現 Metaspace
- 存儲:類結構信息、運行時常量池、靜態變量、JIT 編譯代碼
- -XX:MetaspaceSize/-XX:MaxMetaspaceSize 控制
4. 運行時常量池 - 符號引用與字符串駐留
- 每個類一個運行時常量池,屬於方法區一部分
String.intern() 將字符串放入常量池(JDK 7+ 在堆中)
- Class 文件常量池(編譯期) → 運行時常量池(加載時)
5. 直接內存 - NIO 零拷貝利器
- 非 JVM 運行時數據區,但常被使用
DirectByteBuffer 通過 Native 分配堆外內存,避免堆內外拷貝
- 受限於 -XX:MaxDirectMemorySize 與物理內存
對象創建內存分配流程
- 類加載檢查 → 解析符號引用
- 堆中劃分內存(指針碰撸/空閒列表)
- 初始化零值 → 設置對象頭(哈希碼、GC 信息、鎖狀態、類指針)
- 執行
<init> 方法(字段賦值、構造器)
- 返回引用給棧幀局部變量表
常見誤區澄清
- 「基本類型在棧,引用類型在堆」不準確:基本類型作為局部變量在棧,作為對象字段在堆
- 字符串常量池 JDK 7 起移至堆,非方法區
- 方法區是規範,Metaspace/PermGen 是實現
掌握內存區域分佈,是 JVM 調優與故障診斷的基石。