面試被問(wèn):OOM 類(lèi)型有哪些?怎么答?
面試官:OOM類(lèi)型有哪些?
你:就是老年代放不下了嘛!
面試官:等消息吧!
OOM(Out Of Memory) 錯(cuò)誤有多種類(lèi)型,每種類(lèi)型對(duì)應(yīng)不同的內(nèi)存區(qū)域或觸發(fā)場(chǎng)景。以下是常見(jiàn)的 OOM 類(lèi)型及其產(chǎn)生原因:
1. java.lang.OutOfMemoryError: Java heap space
觸發(fā)原因:堆內(nèi)存(存放對(duì)象實(shí)例)不足,無(wú)法分配新對(duì)象。
典型場(chǎng)景:
- 內(nèi)存泄漏:對(duì)象被無(wú)意長(zhǎng)期引用(如靜態(tài)集合、未關(guān)閉的資源),無(wú)法被 GC 回收。
- 堆大小不足:JVM 堆參數(shù)(-Xmx)設(shè)置過(guò)小,或程序需要處理的數(shù)據(jù)量超出預(yù)期。
- 大對(duì)象分配:一次性申請(qǐng)超大對(duì)象(如大數(shù)組)。
示例:
// 不斷向集合中添加對(duì)象導(dǎo)致堆溢出
List<Object> list = new ArrayList<>();
while (true) {
list.add(new Object());
}
解決方案:
- 檢查內(nèi)存泄漏(使用 jmap + MAT 分析堆轉(zhuǎn)儲(chǔ))。
- 調(diào)整堆大小(-Xmx 和 -Xms)。
- 優(yōu)化代碼邏輯,減少對(duì)象生命周期。
2. java.lang.OutOfMemoryError: Metaspace(Java 8+)或 PermGen space(Java 7-)
觸發(fā)原因:元空間(Metaspace)或永久代(PermGen)內(nèi)存不足,用于存儲(chǔ)類(lèi)元數(shù)據(jù)、方法信息等。
典型場(chǎng)景:
- 動(dòng)態(tài)生成大量類(lèi)(如使用 CGLib、反射、動(dòng)態(tài)代理)。
- 類(lèi)加載器未正確釋放(如頻繁部署的 Web 應(yīng)用導(dǎo)致舊類(lèi)未卸載)。
示例:
// 使用 CGLib 動(dòng)態(tài)生成大量代理類(lèi)
Enhancer enhancer = new Enhancer();
while (true) {
enhancer.setSuperclass(OOM.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args)));
enhancer.create();
}
解決方案:
- 調(diào)整元空間大?。?XX:MaxMetaspaceSize)。
- 檢查類(lèi)加載器泄漏或動(dòng)態(tài)類(lèi)生成邏輯。
3. java.lang.OutOfMemoryError: Direct buffer memory
觸發(fā)原因:直接內(nèi)存(Direct Memory,通過(guò) ByteBuffer.allocateDirect() 分配)耗盡。
典型場(chǎng)景:
- 頻繁申請(qǐng)直接內(nèi)存但未及時(shí)釋放(需依賴(lài) System.gc() 或 Cleaner 機(jī)制)。
- JVM 直接內(nèi)存參數(shù)(-XX:MaxDirectMemorySize)設(shè)置過(guò)小。
示例:
// 不斷申請(qǐng)直接內(nèi)存
List<ByteBuffer> buffers = new ArrayList<>();
while (true) {
buffers.add(ByteBuffer.allocateDirect(1024 * 1024)); // 1MB
}
解決方案:
- 檢查直接內(nèi)存使用代碼,確保及時(shí)釋放。
- 調(diào)整 -XX:MaxDirectMemorySize。
4. java.lang.OutOfMemoryError: Unable to create new native thread
觸發(fā)原因:操作系統(tǒng)限制線程數(shù)量,無(wú)法創(chuàng)建新線程。
典型場(chǎng)景:
- 線程數(shù)超過(guò)系統(tǒng)限制(如 Linux 的 ulimit -u)。
- 每個(gè)線程的棧內(nèi)存(-Xss)設(shè)置過(guò)大,導(dǎo)致總內(nèi)存占用超出。
示例:
// 無(wú)限創(chuàng)建線程
while (true) {
new Thread(() -> {
try { Thread.sleep(1000000); } catch (InterruptedException e) {}
}).start();
}
解決方案:
- 減少線程數(shù)(使用線程池)。
- 調(diào)整 -Xss 減小線程棧大小。
- 修改系統(tǒng)線程數(shù)限制。
5. java.lang.OutOfMemoryError: Requested array size exceeds VM limit
- 觸發(fā)原因:嘗試分配超過(guò) JVM 限制的數(shù)組(通常接近 Integer.MAX_VALUE)。
- 典型場(chǎng)景:錯(cuò)誤計(jì)算數(shù)組長(zhǎng)度,如 new int[Integer.MAX_VALUE]。
解決方案:檢查數(shù)組長(zhǎng)度計(jì)算邏輯,使用合理的數(shù)據(jù)結(jié)構(gòu)。
6. java.lang.OutOfMemoryError: GC Overhead limit exceeded
觸發(fā)原因:GC 頻繁執(zhí)行但回收效率極低(如 98% 時(shí)間用于 GC,僅回收 2% 內(nèi)存)。
典型場(chǎng)景:堆內(nèi)存幾乎被占滿,且存在大量無(wú)法回收的對(duì)象(內(nèi)存泄漏)。
解決方案:檢查內(nèi)存泄漏或優(yōu)化 GC 策略(如調(diào)整堆大小、更換垃圾回收器)。
7. java.lang.OutOfMemoryError: CodeCache(JIT 編譯代碼緩存溢出)
觸發(fā)原因:JIT 編譯器生成的本地代碼占滿代碼緩存區(qū)。
典型場(chǎng)景:高頻動(dòng)態(tài)編譯大量方法(如復(fù)雜的熱點(diǎn)代碼)。
解決方案:
- 調(diào)整代碼緩存大?。?XX:ReservedCodeCacheSize)。
- 關(guān)閉分層編譯(-XX:-TieredCompilation)。
總結(jié)
OOM 的根本原因是 JVM 內(nèi)存區(qū)域不足 或 資源耗盡,需結(jié)合錯(cuò)誤類(lèi)型分析具體內(nèi)存區(qū)域(堆、元空間、直接內(nèi)存等)。
排查時(shí)可通過(guò)以下步驟:
- 確定 OOM 類(lèi)型(通過(guò)錯(cuò)誤日志)。
- 使用工具分析(如 jstat、jmap、VisualVM、MAT)。
- 調(diào)整 JVM 參數(shù)或優(yōu)化代碼邏輯。