Java中的OutOfMemoryError(OOM)是當JVM內存不足時拋出的錯誤。本文將全面剖析JVM中產生OOM的各種情況,包括堆內存溢出、方法區溢出、棧溢出等,并提供詳細的診斷方法和解決方案。
一、OOM基礎概念
1.1 OOM錯誤類型
- Java中的OOM是java.lang.OutOfMemoryError的子類,常見的有:
- Java heap space:堆空間不足
- GC Overhead limit exceeded:GC效率低下
- PermGen space/Metaspace:方法區溢出
- Unable to create new native thread:線程創建失敗
- Requested array size exceeds VM limit:數組過大
- Direct buffer memory:直接內存溢出
- Code cache:代碼緩存區滿
- Kill process or sacrifice child:Linux系統級限制
二、堆內存溢出(Java heap space)
2.1 產生原因
當對象需要分配到堆內存時,如果堆內存不足且無法通過GC回收足夠空間時拋出。
// 典型示例
public class HeapOOM {public static void main(String[] args) {List<Object> list = new ArrayList<>();while(true) {list.add(new byte[1024*1024]); // 每次分配1MB}}
}
2.2 錯誤信息
java.lang.OutOfMemoryError: Java heap space
2.3 解決方案
調整堆大小:
-Xms256m -Xmx1024m ?# 初始堆256MB,最大堆1GB
內存分析:
使用jmap獲取堆轉儲:
jmap -dump:format=b,file=heap.hprof <pid>
使用MAT/Eclipse Memory Analyzer分析
代碼優化:
避免內存泄漏(如靜態集合、未關閉資源)
使用對象池重用對象
三、GC開銷超限(GC Overhead limit exceeded)
3.1 產生原因
當JVM花費超過98%的時間進行GC,但只恢復了不到2%的堆空間時拋出。
// 典型場景:創建大量生命周期短的對象
public class GCOverheadOOM {public static void main(String[] args) {Map<Key, String> map = new HashMap<>();while(true) {for(int i=0; i<10000; i++) {map.put(new Key(i), "Value"+i);}map.clear(); // 不完全清除}}
}
3.2 錯誤信息
java.lang.OutOfMemoryError: GC Overhead limit exceeded
3.3 解決方案
-
增加堆大小:
? -Xmx2g -XX:+UseG1GC?
-
優化GC策略:
- 對于大量短生命周期對象,使用G1或ZGC
- 調整新生代大小:
-
-XX:NewRatio=2 ?# 新生代占堆的1/3
-
代碼改進:
- 減少臨時對象創建
- 使用更高效的數據結構
四、方法區溢出(Metaspace/PermGen)
4.1 產生原因
JDK8前稱為PermGen space,JDK8+稱為Metaspace,存儲類元數據信息。
// 通過動態生成類填滿方法區
public class MetaspaceOOM {static class OOMObject {}public static void main(String[] args) {while(true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) ->?methodProxy.invokeSuper(o, objects));enhancer.create(); // 動態創建類}}
}
4.2 錯誤信息
?
// JDK7及之前
java.lang.OutOfMemoryError: PermGen space// JDK8+
java.lang.OutOfMemoryError: Metaspace
4.3 解決方案
調整Metaspace大小:
-XX:MaxMetaspaceSize=256m
JDK8前調整PermGen:
-XX:MaxPermSize=128m
減少動態類生成:
緩存動態代理類
限制反射使用
五、線程棧溢出(Unable to create new native thread)
5.1 產生原因
當創建線程數量超過系統限制時發生。
public class ThreadOOM {public static void main(String[] args) {while(true) {new Thread(() -> {try { Thread.sleep(100000); }?catch(InterruptedException e) {}}).start();}}
}
5.2 錯誤信息
java.lang.OutOfMemoryError: Unable to create new native thread
5.3 解決方案
減少線程數量:
使用線程池:
ExecutorService pool = Executors.newFixedThreadPool(100);
調整系統限制:
ulimit -u ?# 查看最大線程數
ulimit -u 2048 ?# 設置最大線程數
減少棧大小:
-Xss256k ?# 默認1MB,減少可創建更多線程
六、直接內存溢出(Direct buffer memory)
6.1 產生原因
NIO使用的直接內存(堆外內存)不足時拋出。
public class DirectMemoryOOM {public static void main(String[] args) {// 繞過DirectByteBuffer限制,直接分配內存List<ByteBuffer> buffers = new ArrayList<>();while(true) {buffers.add(ByteBuffer.allocateDirect(1024*1024)); // 1MB}}
}
6.2 錯誤信息
java.lang.OutOfMemoryError: Direct buffer memory
6.3 解決方案
調整直接內存大小:
-XX:MaxDirectMemorySize=256m
顯式回收:
((DirectBuffer)buffer).cleaner().clean();
使用池化技術:
Netty的ByteBuf池
七、數組過大溢出(Requested array size exceeds VM limit)
7.1 產生原因
嘗試分配超過JVM限制的數組。
public class ArraySizeOOM {public static void main(String[] args) {int[] arr = new int[Integer.MAX_VALUE]; // 約2^31-1個元素}
}
7.2 錯誤信息
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
7.3 解決方案
減小數組大小:
分塊處理大數據
使用集合替代:
List<Integer> list = new ArrayList<>();
調整數據結構:
使用數據庫或文件存儲
八、代碼緩存溢出(Code cache)
8.1 產生原因
JIT編譯的代碼填滿代碼緩存區。
// 通常由大量方法被JIT編譯導致
public class CodeCacheOOM {public static void main(String[] args) {// 需要大量方法編譯的代碼}
}
8.2 錯誤信息
java.lang.OutOfMemoryError: Code cache
8.3 解決方案
增加代碼緩存大小:
-XX:ReservedCodeCacheSize=256m
減少編譯閾值:
-XX:CompileThreshold=10000
關閉分層編譯:
-XX:-TieredCompilation
九、系統級OOM(Kill process or sacrifice child)
9.1 產生原因
Linux系統的OOM Killer終止進程。
dmesg | grep -i kill
輸出示例:
Out of memory: Kill process 12345 (java) score 999 or sacrifice child
9.2 解決方案
增加系統內存
調整OOM Killer策略:
echo -17 > /proc/[pid]/oom_adj
限制容器內存(Docker):
docker run -m 2g my-java-app
十、OOM診斷工具鏈
工具 | 用途 | 示例命令 |
jstat | 監控內存和GC | jstat -gcutil <pid> 1000 |
jmap | 堆轉儲 | jmap -dump:live,format=b,file=heap.hprof <pid> |
jvisualvm | 可視化分析 | 圖形化界面 |
MAT | 內存分析 | 分析hprof文件 |
jcmd | 多功能工具 | jcmd <pid> VM.native_memory |
十一、OOM預防最佳實踐
代碼層面:
避免內存泄漏(監聽器、靜態集合)
及時關閉資源(數據庫連接、文件流)
使用WeakReference處理緩存
JVM配置:
# 基礎配置示例
-Xms1g -Xmx2g -XX:MaxMetaspaceSize=256m?
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
監控預警:
JMX監控堆內存使用
Prometheus + Grafana監控體系
設置合理的GC日志監控:
-Xlog:gc*:file=gc.log:time:filecount=5,filesize=10M
十二、總結
OOM類型與對應解決方案速查表:
OOM類型 | 相關內存區域 | 典型解決方案 |
Java heap space | 堆 | 增大堆,修復內存泄漏 |
GC Overhead | 堆 | 優化GC策略,減少對象創建 |
Metaspace/PermGen | 方法區 | 增大Metaspace,減少動態類生成 |
Unable to create thread | 棧 | 減少線程數,調整-Xss |
Direct buffer | 直接內存 | 增大MaxDirectMemorySize,顯式回收 |
Array size | 堆 | 減小數組尺寸,分塊處理 |
Code cache | JIT代碼緩存 | 增大ReservedCodeCacheSize |
System OOM | 系統內存 | 增加物理內存,調整OOM Killer |