首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

JVM 內存溢出實戰:手寫代碼觸發 Heap/Stack/Metaspace/Direct Memory OOM 異常全解析

     編輯:Java 綜合教學

JVM 內存溢出實戰:五大區域 OOM 觸發代碼與分析

理解 JVM 運行時數據區是排查 OOM 的前提。本文通過可運行代碼,分別觸發 Java 堆、虛擬機棧、方法區/元空間、運行時常量池、直接內存 五大區域的 OutOfMemoryError,並解析異常特徵與排查思路。

JVM 運行時數據區速覽

  • 程序計數器:唯一不規定 OOM 的區域。
  • Java 堆:對象實例、數組分配主戰場,GC 主管區域。
  • 虛擬機棧:線程私有,存棧幀;深度過大拋 StackOverflowError,創建過多線程拋 OOM。
  • 本地方法棧:Native 方法服務,行為類似虛擬機棧。
  • 方法區:類元信息、常量、靜態變量、JIT 編譯代碼。JDK 8+ 用元空間實現,機器內存為限。
  • 運行時常量池:方法區一部分,存字面量、符號引用。
  • 直接內存:NIO DirectByteBuffer 分配,受 -XX:MaxDirectMemorySize 限制。

1. Java Heap Space OOM

核心:不斷創建對象 + 保持 GC Root 可達。

// VM Args: -Xms20m -Xmx20m
public class HeapOOM {
    static class OOMObject {}
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        while (true) list.add(new OOMObject()); // GC Root 為 list 靜態引用
    }
}

異常特徵:java.lang.OutOfMemoryError: Java heap space,堆轉儲文件分析可見大量 OOMObject。

2. 虛擬機棧 OOM

場景 A:棧幀過深 -> StackOverflowError

// VM Args: -Xss256k
public class StackOverflow {
    private int depth = 0;
    void leak() { depth++; leak(); }
    public static void main(String[] args) {
        try { new StackOverflow().leak(); }
        catch (Throwable e) { System.out.println("棧深度: " + new StackOverflow().depth); throw e; }
    }
}

異常:java.lang.StackOverflowError,非 OOM 但屬棧內存耗盡。

場景 B:瘋狂創建線程 -> OOM: unable to create new native thread

// VM Args: -Xss256k
public class ThreadOOM {
    void dontStop() { while (true) {} }
    public static void main(String[] args) {
        while (true) new Thread(new ThreadOOM()::dontStop).start();
    }
}

警告:Windows 下極易導致系統假死,謹慎運行!Linux 下受 ulimit -u、/proc/sys/kernel/pid_max 限制。

3. 方法區/元空間 OOM

原理:動態生成大量類(CGLib、JSP、動態代理、OSGi 等場景)。JDK 8+ 設置 -XX:MaxMetaspaceSize。

// VM Args: -XX:MaxMetaspaceSize=10m
// 需引入 cglib 依賴
public class MetaspaceOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) -> proxy.invokeSuper(obj, args1));
            enhancer.create(); // 每次生成一個新子類 Class
        }
    }
    static class OOMObject {}
}

異常:java.lang.OutOfMemoryError: Metaspace(JDK 8+),JDK 7 顯示 PermGen space。

4. 運行時常量池 OOM

JDK 6 永久代:String.intern() 會將字符串放入永久代常量池,限制 -XX:PermSize=10M -XX:MaxPermSize=10M 可觸發 PermGen space。
JDK 7/8:字符串常量池已移至堆,上述代碼會觸發 Java heap space,而非方法區 OOM。需關注版本差異。

5. 直接內存 OOM

觸發:ByteBuffer.allocateDirect(100MB) 循環調用,超過 -XX:MaxDirectMemorySize(默認等於 -Xmx)。
異常:java.lang.OutOfMemoryError: Direct buffer memory。
排查:NIO、Netty、RocketMQ 等大量使用直接內存的組件易發生泄漏,需檢查 Cleaner、Reference 釋放邏輯。

排查工具鏈

  1. jps -l 定位進程 PID
  2. jstat -gcutil PID 1000 觀察 GC 頻率、各區佔用
  3. jmap -dump:live,format=b,file=heap.hprof PID 導出堆轉儲
  4. MAT (Memory Analyzer Tool) / JProfiler 分析支配者樹、GC Roots 路徑
  5. jstack PID 分析線程狀態、死鎖、棧深度
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved