棧與堆是 Java 在 RAM 中存放數據的兩大區域,Java 自動管理,程序員不可直接控制。本文從定義、優缺點、數據共享、String 創建差異、內存申請響應、大小限制、效率對比、存儲內容、存取效率九大維度深度對比,並以經典比喻收尾。
| 特性 | 堆 | 棧 |
|---|---|---|
| 定義 | 運行時數據區,對象實例分配地(new/newarray 等指令) | 存放基本類型變量、對象引用(句柄) |
| 管理 | 垃圾回收器自動回收 | 系統自動分配釋放 |
| 優勢 | 動態分配,大小生存期無需預知 | 存取速度極快(僅次寄存器),數據可共享 |
| 劣勢 | 動態分配導致存取較慢 | 大小生存期必須確定,缺乏靈活性 |
int a = 3; int b = 3; // 編譯器處理: // 1. 創建 a 引用,棧中無 3 → 存入 3,a 指向 3 // 2. 創建 b 引用,棧中已有 3 → b 直接指向 3 // 結果:a、b 同時指向同一棧內存地址 a = 4; // 編譯器重新搜索 4,無則存入,a 指向 4 // b 仍指向 3,互不影響
本質:編譯器優化,節省空間。與對象引用共享本質不同——對象引用修改內部狀態會相互影響,基本類型值不可變。
// 方式一:字面量(常量池/棧)
String str = "abc";
// 1. 創建引用 str
// 2. 棧/常量池查找 "abc",無則存入
// 3. str 指向 "abc"
// 多次執行:僅創建一個 "abc" 對象,所有引用共享
// 方式二:new 對象(堆)
String str = new String("abc");
// 每次調用:在堆中創建全新對象,無論值是否相等
String s1 = "abc";
String s2 = "abc";
s1 == s2; // true,指向同一常量池對象
String s3 = new String("abc");
String s4 = new String("abc");
s3 == s4; // false,堆中兩個不同對象
s3.equals(s4); // true,值相等
String str = "abc" 不一定創建新對象,可能指向已有常量池對象。new String() 保證每次創建新對象。StringBuffer/StringBuilder,避免產生大量臨時對象。| 項目 | 棧 | 堆 |
|---|---|---|
| 結構 | 向低地址擴展,連續內存區 | 向高地址擴展,非連續(鏈表管理) |
| 默認大小 | Windows 下 1M/2M(編譯期確定常數) | 受限於虛擬內存,極大靈活 |
| 溢出 | 超過剩余空間 → overflow | 極難耗盡 |
char c[] = "1234567890"; // 棧上數組 char *p = "1234567890"; // 指向堆/數據段 a = c[1]; // 反編譯:mov cl, byte ptr [ebp-0Fh] → 直接讀取 a = p[1]; // 反編譯:mov edx, dword ptr [ebp-14h] → mov al, byte ptr [edx+1] → 兩步訪問
棧上數組單指令訪問,指針需先取地址再取值,顯著慢於棧。
棧 = 去飯館吃飯:點菜(申請)、付錢、吃(使用)、走人。快捷但自由度低(菜單固定)。
堆 = 自己做菜:買菜、洗菜、切菜、炒菜、洗碗。麻煩但自由度高(隨心所欲)。