Java 虛擬機在執行程序時會將管理的內存劃分為若干個數據區域。根據《Java 虛擬機規範》,主要分為五大區域:
| 區域 | 屬性 | 生命週期 |
|---|---|---|
| 程序計數器、Java 棧、本地方法棧 | 線程私有 | 隨線程創建/銷毀 |
| 堆、方法區 | 線程共享 | 隨 JVM 啟動/關閉 |
方法區不僅存類結構信息,還包含運行時常量池。Class 文件中的常量池編譯期生成,運行期加載進方法區。關鍵點:運行時常量池具備動態性,可通過 String.intern() 將新字符串常量放入池中。
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,堆中兩個對象
s1 = s1.intern();
s2 = s2.intern();
System.out.println(s1 == s2); // true,均指向常量池同一引用
JIT 編譯器通過逃逸分析判斷對象作用域:
啟用參數:-XX:+DoEscapeAnalysis(JDK 7+ 默認開啟)。
| 方式 | 原理 | 優點 | 缺點 | HotSpot 選擇 |
|---|---|---|---|---|
| 句柄訪問 | 棧存句柄池指針 -> 句柄池存對象實例指針 + 類型數據指針 | 對象移動只需修改句柄,棧引用不變 | 雙重指針定位,速度較慢 | 否 |
| 直接指針 | 棧引用直接指向對象實例 | 單次定位,速度快 | 對象移動需更新所有引用 | 是 |
-Xms20m -Xmx20m // 固定堆大小 20MB
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m // 限制元空間
-Xss256k // 縮小棧容量便於測試棧溢出
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./dump.hprof // OOM 導出堆轉儲
大量動態生成類(如 CGLIB 代理、JSP 編譯、OSGi 熱部署)。
// CGLIB 動態代理不緩存類
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false); // 關鍵:禁用緩存
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args));
while (true) {
enhancer.create(); // 不斷生成新類,填滿元空間
}
報錯:java.lang.OutOfMemoryError: Metaspace
byte[] big = new byte[1024 * 1024 * 50]; // 直接申請 50MB,超過 -Xmx20m
報錯:java.lang.OutOfMemoryError: Java heap space
private static int depth = 0;
public static void stackLeak() {
depth++;
stackLeak(); // 無終止條件遞歸
}
報錯:java.lang.StackOverflowError。-Xss 越小越快溢出。
JNI 調用大量本地內存或無限遞歸 Native 方法觸發,模擬較難,常見於 Netty 直接內存洩漏。
-Xms = -Xmx 避免動態縮容擴容抖動,通常設為物理內存 1/2 ~ 2/3。-XX:NewRatio=2 (1:2) 或顯式 -Xmn。-XX:MaxMetaspaceSize=256m 防止類加載洩漏耗盡機器內存。-Xss512k 通常足夠,深度遞歸調大。Parallel GC,大堆低延遲 G1/ZGC。理解 JVM 內存結構是排查 OOM、調優 GC 的基石。實戰中結合 jmap、jstack、VisualVM、Arthas 等工具,定位具體區域溢出原因,再針對性調整參數或修復代碼邏輯(如緩存無界增長、大對象直接入老年代、類加載洩漏等)。