經典面試題:`String s1="123"; String s2="123";` 棧中只佔一塊記憶體 (常量池共享);那麼 `int i1=1; int i2=1;` 呢?本文從 JVM 記憶體結構、編譯器優化、運行時行為三個維度徹底釐清。
| 類型 | 代碼 | 記憶體分配 | 是否共享 | 核心原因 |
|---|---|---|---|---|
| String (引用型別) | `String s1="123"; String s2="123";` | **堆** (對象) + **棧** (引用) + **常量池** (字面量) | **是** (引用指向同一常量池字面量) | 編譯期確定字面量,類加載時進入常量池,`ldc` 指令推同一引用 |
| int (基本型別) | `int i1=1; int i2=1;` | **棧** (局部變量表槽位) | **否** (兩個獨立槽位,各存 32 位整數 1) | 基本型無引用概念,直接存值;編譯器不會合併局部變量槽位 |
| Integer (包裝類) | `Integer i1=1; Integer i2=1;` | 堆 (對象) + 棧 (引用) + 常量池緩存 (-128~127) | **是** (自動拆箱調用 `Integer.valueOf(1)` 返回緩存對象) | JDK 源碼 `IntegerCache` 緩存小整數,`valueOf` 優先返回緩存 |
每個方法調用建立一個**棧幀**,包含**局部變量表**。`i1`、`i2` 是兩個獨立的 **Slot (槽位,32 位)**,各自存儲 `0x00000001`。編譯器生成的字節碼:
iconst_1 // 推入常量 1
istore_1 // 存入局部變量 1 (i1)
iconst_1 // 再次推入常量 1
istore_2 // 存入局部變量 2 (i2)
兩條 `istore` 寫入不同索引,**物理上就是兩塊獨立記憶體區域**。JVM 規範**不要求**合併相同基本型別值的槽位 (不像常量池合併字面量)。
JIT 編譯器可能將極熱點路徑的局部變量提升到寄存器,甚至常量傳播消除變量,但這是**運行時優化**,不改變語言語義層面的「兩個獨立變量」。語言規範保證 `i1`、`i2` 可獨立修改 (雖然基本型別不可變,但槽位可重寫),故必須分配獨立存儲。
final int a = 1;
final int b = 1;
// 編譯器常量折疊:a+b 在編譯期直接算為 2,字節碼無加法指令
// 但 a、b 在局部變量表仍佔兩個槽位 (除非優化消除)
**字串常量池是 JVM 為不可變字面量設計的特殊共享區**;**基本型別直接在棧槽位存值,無共享機制**。理解「值存儲 vs 引用存儲」、「常量池 vs 局部變量表」的本質區別,才能準確預測記憶體行為,避免在性能調優、對象比較、並發編程中踩坑。