最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java?OOM問題定位到徹底根治完全解析

 更新時間:2026年05月13日 10:52:52   作者:沒有逆稱  
在Java中OOM是指JVM無法為應(yīng)用程序分配足夠的內(nèi)存,導致程序崩潰,解決OOM問題需要從多個角度分析并優(yōu)化應(yīng)用程序的內(nèi)存使用,這篇文章主要介紹了Java OOM問題定位到徹底根治的相關(guān)資料,需要的朋友可以參考下

前言

java.lang.OutOfMemoryError —— 相信每一個 Java 開發(fā)者都被它折磨過。生產(chǎn)環(huán)境凌晨三點的告警,頻繁 Full GC 之后的服務(wù)宕機,排查了半天發(fā)現(xiàn)是幾年前留下的"祖?zhèn)鞔a"……

OOM 的可怕之處不在于錯誤本身,而在于它往往是長時間問題積累的集中爆發(fā),排查鏈路長、現(xiàn)場難復(fù)現(xiàn)。

本文結(jié)合實際生產(chǎn)經(jīng)驗,系統(tǒng)梳理 6 種常見 OOM 類型,對每一種都給出:觸發(fā)原因 → 排查手段 → 解決方案 → 避坑建議,力求一文搞定。

1. OOM 全景速覽

Java 的內(nèi)存結(jié)構(gòu)決定了 OOM 有多個"爆炸點",先來一張全景圖:

JVM 內(nèi)存模型全景圖

GC 觸發(fā)區(qū)域:Young GC(Eden滿)→ Old GC(Old滿)→ Full GC(整個堆+Metaspace)

OOM 類型觸發(fā)區(qū)域常見程度
Java heap space堆內(nèi)存?????
GC overhead limit exceeded堆內(nèi)存????
Metaspace元空間???
Unable to create new native thread線程???
Direct buffer memory堆外內(nèi)存??
StackOverflowError??

2. Java Heap Space — 堆內(nèi)存溢出

2.1 錯誤表現(xiàn)

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.ArrayList.grow(ArrayList.java:265)
    ...

2.2 觸發(fā)原因

① 內(nèi)存泄漏(最常見)

對象持有引用,GC 無法回收,隨時間積累直到堆撐爆。

典型場景:

  • 靜態(tài)集合無限增長(static List/Map 只增不減)
  • 未關(guān)閉的資源(Connection、InputStream)
  • 監(jiān)聽器注冊后未注銷(Event Listener)
  • 線程局部變量 ThreadLocal 未 remove
// ?? 危險代碼:靜態(tài) Map 充當"黑洞"
public class CacheManager {
    private static final Map<String, Object> CACHE = new HashMap<>();
    
    public void add(String key, Object value) {
        CACHE.put(key, value); // 只進不出,遲早 OOM
    }
}

② 內(nèi)存溢出(數(shù)據(jù)量真的太大)

  • 一次性加載超大數(shù)據(jù)集到內(nèi)存(如全表查詢幾千萬條數(shù)據(jù))
  • 生成超大文件(Excel、PDF)全部在內(nèi)存中處理

2.3 排查手段

Step 1:開啟 OOM 時自動 Dump

# JVM 啟動參數(shù)中加入
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/logs/heapdump.hprof

Step 2:分析 Heap Dump

推薦工具:JProfiler(IDEA 官方推薦,可視化極強)

  1. 打開 JProfiler,選擇 “Open a Heap Dump” 導入 .hprof 文件
  2. 點擊 “Biggest Objects” → 直接展示占用內(nèi)存最多的對象
  3. 使用 “Dominator Tree” → 分析對象引用樹,定位泄漏根因
  4. “References” 視圖 → 查看誰在引用大對象,追溯到具體代碼行

Step 3:線上快速定位(不重啟)

# 手動 dump(需要進程 PID)
jmap -dump:format=b,file=/tmp/heap.hprof <PID>

# 查看堆內(nèi)存概要
jmap -heap <PID>

# 實時查看 GC 情況
jstat -gcutil <PID> 1000 10

2.4 解決方案

方案適用場景
修復(fù)內(nèi)存泄漏代碼根本解法,強烈推薦
合理設(shè)置堆大小 -Xmx內(nèi)存真不夠用時臨時擴容
分批處理大數(shù)據(jù)避免全量加載
引入緩存淘汰策略WeakHashMap、Guava Cache 替代普通 Map
流式處理(Stream/游標)大文件、大查詢場景
// ? 正確姿勢:使用 MyBatis 游標批量處理
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)
@Select("SELECT * FROM big_table")
Cursor<BigData> streamAll();

// 配合使用
try (Cursor<BigData> cursor = mapper.streamAll()) {
    for (BigData data : cursor) {
        process(data); // 逐條處理,不全量加載
    }
}

3. GC Overhead Limit Exceeded — GC 開銷超限

3.1 錯誤表現(xiàn)

java.lang.OutOfMemoryError: GC overhead limit exceeded

3.2 觸發(fā)原因

JVM 默認閾值:GC 耗時超過 98% 的時間,而回收的內(nèi)存不到 2%,連續(xù)多次后觸發(fā)此 OOM。

本質(zhì)上是"堆內(nèi)存已滿,GC 拼命跑卻白忙活"的信號,往往先于 heap space OOM 出現(xiàn)。

3.3 排查與解決

排查方式同 堆內(nèi)存溢出,核心是找到內(nèi)存泄漏點。

臨時規(guī)避(不推薦長期使用):

# 關(guān)閉此限制檢測(治標不治本)
-XX:-UseGCOverheadLimit

根治方向:

  • 調(diào)大堆內(nèi)存 -Xmx
  • 優(yōu)化對象創(chuàng)建,減少短生命周期大對象
  • 使用合適的 GC 算法(G1/ZGC)

4. Metaspace — 元空間溢出

4.1 錯誤表現(xiàn)

java.lang.OutOfMemoryError: Metaspace

Java 8 之前是 PermGen space(永久代),Java 8 之后改為 Metaspace(元空間,使用本地內(nèi)存)。

4.2 觸發(fā)原因

Metaspace 存儲類的元數(shù)據(jù)(類名、方法、字段信息等)。以下情況會導致類爆炸:

  • 動態(tài)代理/字節(jié)碼增強框架:如 Spring AOP、CGLib、ASM 運行時生成大量代理類
  • Groovy 動態(tài)腳本:每次執(zhí)行都生成新的 Class
  • 熱部署/反復(fù) reload:舊類無法被 GC(ClassLoader 有強引用)
  • OSGi 插件化架構(gòu):Bundle 頻繁加載卸載
// ?? 危險:在循環(huán)中動態(tài)生成類
for (int i = 0; i < 100000; i++) {
    // 每次都生成新的代理類,ClassLoader 持有引用無法 GC
    Object proxy = Proxy.newProxyInstance(
        classLoader, interfaces, handler
    );
}

4.3 排查手段

# 查看 Metaspace 使用情況
jstat -gcmetacapacity <PID>

# 查看加載了多少類
jstat -class <PID>

# JVM 參數(shù):開啟詳細 GC 日志
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/gc.log

JVisualVMArthas 查看類加載數(shù)量趨勢,若持續(xù)增長不下降 → 類泄漏確認。

4.4 解決方案

# 設(shè)置 Metaspace 上限,防止無限增長吃掉系統(tǒng)內(nèi)存
-XX:MaxMetaspaceSize=256m

# 設(shè)置初始大小,減少頻繁擴容
-XX:MetaspaceSize=128m

代碼層面:

  • 復(fù)用 ClassLoader,避免頻繁創(chuàng)建
  • 腳本引擎(Groovy/MVEL)使用緩存,不每次新建
  • 檢查框架版本,部分老版本 CGLib 有類泄漏 Bug

5. Unable to Create New Native Thread — 無法創(chuàng)建線程

5.1 錯誤表現(xiàn)

java.lang.OutOfMemoryError: unable to create new native thread

5.2 觸發(fā)原因

這個 OOM 不是 Java 堆內(nèi)存不足,而是:

  • 系統(tǒng)線程數(shù)達到上限(Linux 默認每進程約 1024 個線程)
  • 線程泄漏:線程池配置不當,任務(wù)堆積導致線程數(shù)失控
  • 每個線程占用棧內(nèi)存(默認 512KB~1MB),線程過多耗盡系統(tǒng)內(nèi)存

5.3 排查手段

# 查看當前進程線程數(shù)
ps -eLf | grep java | wc -l

# 查看系統(tǒng)允許的最大線程數(shù)
cat /proc/sys/kernel/threads-max

# 查看每個用戶的線程限制
ulimit -u

# Arthas 查看線程堆棧(神器)
java -jar arthas-boot.jar
# 進入后執(zhí)行:
thread -n 10  # 查看 CPU 占用最高的 10 個線程
thread -b     # 查找死鎖

jstack 分析線程 Dump:

jstack <PID> > /tmp/thread.dump
# 然后統(tǒng)計各線程狀態(tài)
grep "java.lang.Thread.State" /tmp/thread.dump | sort | uniq -c | sort -rn

5.4 解決方案

① 調(diào)大系統(tǒng)線程限制

# 臨時修改(重啟失效)
ulimit -u 65535

# 永久修改 /etc/security/limits.conf
* soft nproc 65535
* hard nproc 65535

② 規(guī)范線程池使用

// ? 錯誤:每次請求都創(chuàng)建新線程
new Thread(() -> doTask()).start();

// ? 正確:統(tǒng)一線程池管理
@Bean
public ThreadPoolExecutor taskExecutor() {
    return new ThreadPoolExecutor(
        10,          // corePoolSize
        50,          // maximumPoolSize
        60L,         // keepAliveTime
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(1000),  // 有界隊列!
        new ThreadFactoryBuilder().setNameFormat("task-%d").build(),
        new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略
    );
}

?? 強調(diào):禁止使用 Executors.newCachedThreadPool(),其最大線程數(shù)為 Integer.MAX_VALUE,高并發(fā)下必炸。

6. Direct Buffer Memory — 直接內(nèi)存溢出

6.1 錯誤表現(xiàn)

java.lang.OutOfMemoryError: Direct buffer memory

6.2 觸發(fā)原因

DirectByteBuffer 分配的是堆外內(nèi)存(Off-Heap),不受 -Xmx 限制,由 -XX:MaxDirectMemorySize 控制。

常見場景:

  • Netty / NIO 框架大量使用直接內(nèi)存
  • 手動調(diào)用 ByteBuffer.allocateDirect() 未及時釋放
  • 直接內(nèi)存回收依賴 GC,但 GC 不頻繁時堆外內(nèi)存不釋放
// ?? 危險:頻繁申請直接內(nèi)存不釋放
for (int i = 0; i < 10000; i++) {
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MB
    // 忘記 ((DirectBuffer) buffer).cleaner().clean()
}

6.3 排查與解決

# 查看直接內(nèi)存使用(通過 JMX)
jcmd <PID> VM.native_memory summary

# 設(shè)置直接內(nèi)存上限
-XX:MaxDirectMemorySize=512m

代碼層面:

  • Netty 使用 PooledByteBufAllocator 復(fù)用 Buffer
  • 手動申請的 DirectBuffer 使用完后調(diào)用 cleaner().clean() 或等待 GC
  • 升級 Netty 版本,新版本內(nèi)存管理更完善

7. Stack Overflow — 棧溢出

7.1 錯誤表現(xiàn)

java.lang.StackOverflowError
    at com.example.Fibonacci.fib(Fibonacci.java:5)
    at com.example.Fibonacci.fib(Fibonacci.java:5)
    ...(重復(fù) N 次)

嚴格來說 StackOverflowErrorError 不是 OOM,但生產(chǎn)環(huán)境同樣會引發(fā)服務(wù)不可用。

7.2 觸發(fā)原因

  • 無限遞歸:遞歸終止條件缺失或錯誤
  • 遞歸深度過大:數(shù)據(jù)結(jié)構(gòu)深度超過棧容量(默認約 512~1024 幀)
  • 對象循環(huán)引用 + JSON 序列化(如 toString/equals 觸發(fā)無限調(diào)用)
// ?? 死遞歸
public int factorial(int n) {
    return n * factorial(n - 1); // 忘記 if(n == 0) return 1;
}

7.3 解決方案

// ? 方案一:修復(fù)遞歸終止條件
public int factorial(int n) {
    if (n <= 0) return 1; // 終止條件
    return n * factorial(n - 1);
}

// ? 方案二:改為循環(huán)(深度無限制)
public int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// ? 方案三:尾遞歸優(yōu)化(Java 不原生支持,可用 Trampoline 模式)

調(diào)大棧深度(謹慎):

-Xss2m  # 每個線程棧大小,默認 512K,調(diào)大會減少最大線程數(shù)

8. 生產(chǎn)級排查工具箱

8.1 Arthas —— 線上診斷神器

# 下載并啟動
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

# 常用命令
dashboard          # 實時查看 JVM 狀態(tài)
heap               # 查看堆內(nèi)存
thread -n 5        # CPU 最高的 5 個線程
jad com.example.Foo # 反編譯線上類
watch com.example.Foo methodName '{params,returnObj}' # 監(jiān)控方法入?yún)⒊鰠?
trace com.example.Foo methodName  # 追蹤方法調(diào)用鏈路耗時

8.2 常用工具對比

工具適用場景優(yōu)點
JProfilerIDEA 集成 Heap Dump 分析可視化極強,IDEA 官方推薦
Arthas線上動態(tài)診斷無需重啟,功能強大
JVisualVM本地可視化監(jiān)控JDK 自帶,圖形化
jstack線程 Dump 分析簡單直接,排查死鎖
jmap堆內(nèi)存快照配合 JProfiler 使用
jstatGC 實時監(jiān)控輕量,適合快速判斷
Prometheus + Grafana長期監(jiān)控告警生產(chǎn)環(huán)境標配

8.3 推薦 JVM 參數(shù)配置(生產(chǎn)模板)

# 堆內(nèi)存
-Xms2g -Xmx2g

# GC 選擇(推薦 G1)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

# OOM 自動 Dump
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/logs/

# GC 日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/gc-%t.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=20m

# Metaspace
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m

# 直接內(nèi)存
-XX:MaxDirectMemorySize=512m

9. 總結(jié)與最佳實踐

OOM 處理決策樹

                    OOM Error Occurred
                          │
                          ▼
            ┌─────────────────────────────┐
            │   查看完整錯誤信息            │
            │   確認 OOM 類型              │
            └─────────────────────────────┘
                          │
          ┌───────────────┼───────────────┐
          ▼               ▼               ▼
   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐
   │ Heap /      │  │ Metaspace / │  │ Direct /    │
   │ GC Overhead │  │ Thread OOM  │  │ Stack OOM   │
   └─────────────┘  └─────────────┘  └─────────────┘
          │               │               │
          ▼               ▼               ▼
   JProfiler        jstat -class     jcmd VM.
   分析 Dump       查看類加載        native_mem
                                       ory
          │               │               │
          └───────┬───────┴───────┬───────┘
                  ▼               ▼
           ┌─────────────┐  ┌─────────────┐
           │  找到根因    │  │  臨時止血    │
           │  修復(fù)代碼    │  │  擴內(nèi)存/重啟 │
           └─────────────┘  └─────────────┘
                  │               │
                  └───────┬───────┘
                          ▼
                ┌─────────────────┐
                │  完善監(jiān)控告警    │
                │  (內(nèi)存>80%預(yù)警) │
                └─────────────────┘

分步處理:

  1. OOM 發(fā)生 → 第一時間查看完整錯誤信息,確認 OOM 類型
  2. 根據(jù)類型選工具
    • heap space / GC overhead → JProfiler 分析 Heap Dump → 找泄漏點
    • Metaspacejstat -gcmetacapacity <PID> 查類加載數(shù)量
    • unable to create native threadps -eLf | grep java | wc -l 查線程數(shù)
    • direct buffer memoryjcmd <PID> VM.native_memory summary 查堆外內(nèi)存
    • StackOverflowjstack <PID> + Arthas thread -n 10 定位遞歸
  3. 臨時止血:擴內(nèi)存參數(shù) / 重啟服務(wù)
  4. 根治方向:修復(fù)代碼 + 完善監(jiān)控告警(內(nèi)存使用率 > 80% 觸發(fā)預(yù)警)

10 條黃金實踐

  1. 始終開啟 -XX:+HeapDumpOnOutOfMemoryError,確保出事時有案可查
  2. 禁止使用 Executors.newCachedThreadPool(),必須使用有界線程池
  3. 靜態(tài)集合慎用,必須有淘汰機制(TTL/LRU/弱引用)
  4. ThreadLocal 必須 remove(),避免內(nèi)存泄漏
  5. 大數(shù)據(jù)集分批處理,拒絕全量加載到內(nèi)存
  6. 資源必須關(guān)閉,使用 try-with-resources 語法
  7. 定期查看 GC 日志,關(guān)注 Full GC 頻率和耗時
  8. 接入監(jiān)控告警,內(nèi)存使用率 > 80% 時觸發(fā)預(yù)警
  9. 壓測先于上線,暴露內(nèi)存問題在生產(chǎn)環(huán)境之前
  10. 代碼 Review 關(guān)注內(nèi)存,重點審查集合、緩存、線程相關(guān)代碼

結(jié)語

OOM 問題往往沒有銀彈,最有效的解法永遠是找到根本原因,而不是盲目擴內(nèi)存。擴內(nèi)存只是推遲了爆炸時間,代碼不改,總有一天還會炸。

希望這篇文章能成為你排查 OOM 時的參考手冊。如果文章對你有幫助,歡迎點贊收藏 ??,有問題歡迎評論區(qū)交流!

參考資料

到此這篇關(guān)于Java OOM問題定位到徹底根治的文章就介紹到這了,更多相關(guān)Java OOM問題解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • spring security的基本原理、配置使用方式

    spring security的基本原理、配置使用方式

    本文詳細介紹了SpringSecurity的基本原理、配置方式、常見使用場景以及常見問題的解決方法,還介紹了SpringSecurity與OAuth2、SSO等系統(tǒng)的整合,并提供了調(diào)試與排錯建議,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • Java設(shè)計模式之抽象工廠模式實例詳解

    Java設(shè)計模式之抽象工廠模式實例詳解

    這篇文章主要介紹了Java設(shè)計模式之抽象工廠模式,結(jié)合實例形式分析了抽象工廠模式的概念、功能、定義與使用方法,需要的朋友可以參考下
    2017-09-09
  • java 中Buffer源碼的分析

    java 中Buffer源碼的分析

    這篇文章主要介紹了java 中Buffer源碼的分析的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Springboot RestTemplate 簡單使用解析

    Springboot RestTemplate 簡單使用解析

    這篇文章主要介紹了Springboot RestTemplate 簡單使用解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-08-08
  • SpringMVC框架實現(xiàn)圖片上傳與下載

    SpringMVC框架實現(xiàn)圖片上傳與下載

    這篇文章主要為大家詳細介紹了SpringMVC框架實現(xiàn)圖片上傳與下載,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • SpringBoot自定義錯誤處理邏輯詳解

    SpringBoot自定義錯誤處理邏輯詳解

    這篇文章主要介紹了SpringBoot自定義錯誤處理邏輯,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-10-10
  • 解讀GC日志中的各項指標用法

    解讀GC日志中的各項指標用法

    這篇文章主要介紹了GC日志中的各項指標用法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-07-07
  • java 跳轉(zhuǎn)搜索的實現(xiàn)示例

    java 跳轉(zhuǎn)搜索的實現(xiàn)示例

    與二分搜索一樣,跳轉(zhuǎn)搜索是一種針對排序數(shù)組的搜索算法,本文主要介紹了java 跳轉(zhuǎn)搜索的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧
    2024-04-04
  • 詳解SpringBoot構(gòu)建Docker鏡像的3種方式

    詳解SpringBoot構(gòu)建Docker鏡像的3種方式

    這篇文章主要介紹了SpringBoot構(gòu)建Docker鏡像的3種方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • Spring框架中Bean的三種配置和實例化方法總結(jié)

    Spring框架中Bean的三種配置和實例化方法總結(jié)

    在Spring框架中,Bean的配置和實例化是很重要的基礎(chǔ)內(nèi)容,掌握各種配置方式,才能靈活管理Bean對象,本文將全面介紹Bean的別名配置、作用范圍配置,以及構(gòu)造器實例化、工廠實例化等方式
    2023-10-10

最新評論

咸阳市| 固原市| 海林市| 贞丰县| 大洼县| 平利县| 邛崃市| 山阳县| 吉木萨尔县| 黑山县| 灌阳县| 奇台县| 沭阳县| 丰县| 新野县| 忻州市| 南澳县| 伊通| 南丹县| 广平县| 乐山市| 太湖县| 威海市| 米泉市| 都兰县| 城市| 聂拉木县| 南皮县| 吉林省| 洛南县| 沈丘县| 贵港市| 涟源市| 民权县| 格尔木市| 广昌县| 金华市| 南通市| 申扎县| 达州市| 常德市|