Java 程式運行在 JVM 上,由 JVM 管理記憶體,這與 C++ 手動管理最大不同。雖有 JVM 管控,但開發者仍需理解其管理機制。本文基於 HotSpot 虛擬機,詳細描述 JVM 記憶體分佈、物件儲存訪問、各區域可能出現的異常。
依據 JVM 規範,管理的記憶體分為以下幾個運行時資料區域:
異常:
StackOverflowError:線程請求棧深度超過最大允許深度。OutOfMemoryError:虛擬機棧可動態擴展時,無法申請足夠記憶體。StackOverflowError 和 OutOfMemoryError。OutOfMemoryError。OutOfMemoryError。OutOfMemoryError。new 指令,從常數池定位類符號引用,檢查類是否已加載初始化,否則執行類加載(失敗拋 ClassNotFoundException)。init 方法完成初始化。HotSpot 中物件分為三部分:
主要異常:OutOfMemoryError、StackOverflowError。除程式計數器外,所有區域皆可能發生。
-Xms20m -Xmx20m -XX:HeapDumpOnOutOfMemoryError
public class HeapOOM {
public static void main(String[] args) {
List<HeapOOM> list = new ArrayList<>();
while (true) {
list.add(new HeapOOM()); // 持有引用,阻止 GC
}
}
}
-Xss128k
// 情況一:無限遞歸增加棧深度
public class StackOverflow {
private int depth = 1;
public void recursive() { depth++; recursive(); }
}
// 情況二:大量區域變數增加棧幀大小
public class StackOOM {
private int depth = 1;
public void recursive() {
double i1, i2, i3...; // 大量變數
depth++; recursive();
}
}
測試證實:無論棧幀過大或容量過小,記憶體無法分配時均拋出 StackOverflowError。
String.intern() 行為差異:
StringBuilder 建立的字串在堆中,故 intern() == str 為 false。intern() 返回堆中同一實例,結果為 true。因此 JDK 1.7+ 下常數池溢出測試實際觸發堆溢出。測試方法區溢出可用 CGLib 動態生成大量類:
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMClass.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
enhancer.create();
}
-XX:MaxDirectMemorySize=30M
public class DirectMemoryOOM {
public static void main(String[] args) {
List<Buffer> buffers = new ArrayList<>();
int i = 0;
while (true) {
System.out.println(++i);
buffers.add(ByteBuffer.allocateDirect(1024 * 1024)); // 每次 1MB
}
}
}
// 第 31 次拋出: java.lang.OutOfMemoryError: Direct buffer memory
本文系統闡述 JVM 記憶體結構、物件儲存訪問機制、各區域異常觸發條件與實戰代碼。參考《深入理解 Java 虛擬機(第二版)》,歡迎指正補充。