理解 Java 記憶體結構是寫出高效能、無記憶體洩漏程式的基石。本文系統整理六大儲存區域、堆與棧的本質差異、物件建立流程、字串常量池機制,並以圖解輔助說明。
| 區域 | 位置 | 特性 | 存放內容 | 執行緒共享性 |
|---|---|---|---|---|
| 程式計數器 (PC Register) | CPU 內部 | 極小、極快 | 當前執行的字節碼指令位址 | 私有 |
| Java 虛擬機棧 (JVM Stack) | RAM | 快、定長、生命週期隨執行緒 | 基本型別變數、物件參考、方法呼叫框架 | 私有 |
| 本地方法棧 (Native Method Stack) | RAM | 服務 native 方法 | C/C++ 呼叫框架 | 私有 |
| Java 堆 (Heap) | RAM | 動態分配、GC 管理、較慢 | 所有 new 出來的物件、陣列 | 共享 |
| 方法區 (Method Area / Metaspace) | RAM | 類結構、常量、靜態變數、JIT 代碼 | Class 元資料、final 常量、static 變數 | 共享 |
| 運行時常量池 (Runtime Constant Pool) | 方法區內 | 每個 Class 一份 | 字面量、符號引用、字串常量 (String.intern()) | 共享 |
┌─────────────────────┬────────────────────┬─────────────────────┐
│ 維度 │ Stack (棧) │ Heap (堆) │
├─────────────────────┼────────────────────┼─────────────────────┤
│ 分配方式 │ 編譯期靜態/連續 │ 運行期動態/非連續 │
│ 速度 │ 極快 (指針移動) │ 較慢 (尋找空閒塊) │
│ 生命週期 │ 隨作用域自動釋放 │ 靠 GC 回收 │
│ 靈活度 │ 低 (大小須知) │ 高 (任意大小/時間) │
│ 存放內容 │ 基本型別、引用 │ 物件實體、陣列 │
│ 共享性 │ 執行緒私有 │ 全域共享 │
│ 溢出異常 │ StackOverflowError │ OutOfMemoryError │
└─────────────────────┴────────────────────┴─────────────────────┘
int a = 3;
int b = 3;
// 編譯器優化:棧中僅存一份 3,a、b 同指向該位置
// 修改 a = 4 時,編譯器重新尋找/建立 4,b 仍指向 3,互不影響
// 此為「值語意共享」,與「物件引用共享」本質不同
HotSpot 64-bit 預設佈局 (Compressed Oops 開啟):
┌────────────────────────────────────────┐
│ Object Header (12 bytes) │
│ ├─ Mark Word (8B) : 鎖狀態、GC 年齡、HashCode │
│ └─ Klass Pointer (4B) : 指向方法區 Class 元資料 │
├────────────────────────────────────────┤
│ Instance Data (實例欄位) │
│ ├─ 基本型別直接儲存 │
│ └─ 物件引用 (4B compressed oop) │
├────────────────────────────────────────┤
│ Padding (對齊填充,8B 倍數) │
└────────────────────────────────────────┘
String.intern() 機制String s = "abc"; → 編譯期進入常量池,同字面量共享同一物件。String s = new String("abc"); → 堆上新建物件,字面量 "abc" 仍在常量池。s.intern():若常量池已有等值字串返回池中引用;否則將字串放入池並返回引用。PermGen 不再存放字串常量。byte[]、StringBuilder、物件池。try-with-resources / Cleaner。-Xlog:gc*、jstat -gcutil、JConsole/VisualVM 觀察 Old 區增長趨勢。new,圖片/資料預載入、複用 byte[] 緩衝區是鐵律。記憶體模型不是枯燥理論,而是效能調優的地圖。掌握「棧存引用、堆存實體、常量池存字面量、方法區存類結構」這條主線,再配合 GC 代際假設,即可在從 J2ME 到雲端伺服器的各種 Java 環境中遊刃有餘。