很多書上說「基本類型存棧,對象存堆」,這其實是片面的。通過一個簡單的例子,我們將揭開 Java 數組內存分配的真相。
public class Test {
public static void main(String[] argv) {
String[] names = { "Michael", "Orson", "Andrew" }; // 長度 3
String[] animal = new String[4]; // 長度 4
names = animal; // 賦值
System.out.println(names.length); // 輸出 4
System.out.println(animal.length); // 輸出 4
}
}
「Java 數組大小不可變」是指數組對象本身在堆中創建後大小固定。但 names 和 animal 只是引用變量(類似 C 指針),它們存儲在棧中,可以指向不同的堆對象。賦值 names = animal 只是讓 names 指向 animal 所指的那個長度為 4 的數組對象,原長度 3 的數組對象因無引用將被 GC 回收。

names 指向長度 3 的數組,animal 指向長度 4 的數組。例中的 "Michael" 等字符串是基本類型嗎?它們是對象,存儲在堆中(或常量池)。正確的表述是:
局部變量(引用類型變量、基本類型變量)放在棧內存中;但變量所引用的對象(包括數組、自定義類對象、String 等)保存在堆內存中。
Java 堆中的對象不能直接訪問,必須通過棧中的引用變量作為中介。
只有當棧內存中的引用變量調用了對象的方法或訪問了對象的屬性時,它才真正表現為對象操作。
public class Animal {
private String name;
private int age;
public Animal(String name, int age) { this.name = name; this.age = age; }
public void info() { System.out.println(name + " " + age); }
}
public class Test {
public static void main(String[] argv) {
Animal[] animal = new Animal[2];
Animal cat = new Animal("cat", 1);
Animal dog = new Animal("dog", 2);
animal[0] = dog;
animal[1] = cat;
// 此時 cat、dog、animal[0]、animal[1] 都是引用變量,指向堆中對象
dog.info(); // 調用方法,輸出 "cat 1"
animal[0].info(); // 同一對象,輸出 "cat 1"
}
}

cat、dog、animal[0]、animal[1] 分別指向不同對象(或 null)。animal[0] 與 dog 指向同一對象,animal[1] 與 cat 指向同一對象。理解這一點,是掌握 Java 內存管理、避免淺拷貝陷阱、優化性能的基石。