如何解決 OutOfMemoryError 內存溢出 —— 原因、定位與解決方案

網羅開發(小紅書、快手、視頻號同名)

??大家好,我是 展菲,目前在上市企業從事人工智能項目研發管理工作,平時熱衷于分享各種編程領域的軟硬技能知識以及前沿技術,包括iOS、前端、Harmony OS、Java、Python等方向。在移動端開發、鴻蒙開發、物聯網、嵌入式、云原生、開源等領域有深厚造詣。

圖書作者:《ESP32-C3 物聯網工程開發實戰》
圖書作者:《SwiftUI 入門,進階與實戰》
超級個體:COC上海社區主理人
特約講師:大學講師,谷歌亞馬遜分享嘉賓
科技博主:華為HDE/HDG

我的博客內容涵蓋廣泛,主要分享技術教程、Bug解決方案、開發工具使用、前沿科技資訊、產品評測與使用體驗。我特別關注云服務產品評測、AI 產品對比、開發板性能測試以及技術報告,同時也會提供產品優缺點分析、橫向對比,并分享技術沙龍與行業大會的參會體驗。我的目標是為讀者提供有深度、有實用價值的技術洞察與分析。

展菲:您的前沿技術領航員
👋 大家好,我是展菲!
📱 全網搜索“展菲”,即可縱覽我在各大平臺的知識足跡。
📣 公眾號“Swift社區”,每周定時推送干貨滿滿的技術長文,從新興框架的剖析到運維實戰的復盤,助您技術進階之路暢通無阻。
💬 微信端添加好友“fzhanfei”,與我直接交流,不管是項目瓶頸的求助,還是行業趨勢的探討,隨時暢所欲言。
📅 最新動態:2025 年 3 月 17 日
快來加入技術社區,一起挖掘技術的無限潛能,攜手邁向數字化新征程!


文章目錄

    • 摘要
    • 先把癥狀搞清楚 — OOM 常見表現
    • 簡單定位思路(快速排查步驟)
    • 瞬間分配大對象導致 OOM
      • 源碼 OOMAllocate.java
      • 編譯與運行(在終端)
    • 內存泄漏模擬(靜態集合持續增長)
      • 源碼 LeakExample.java
      • 編譯與運行
    • 調試與定位工具(實用命令與說明)
      • 生成堆轉儲(heap dump)
      • 快速統計類實例(堆直方圖)
      • 在線分析
      • GC 日志(定位 GC 問題)
    • 常見解決策略(按場景給建議)
      • 快速應急(治標)
      • 根本修復(治本)
      • 特殊類型 OOM 的處理
    • 實戰技巧與最佳實踐(工程化建議)
    • 常見問答(QA)
    • 總結

摘要

Java 程序出現 OutOfMemoryError(OOM)是常見且惱人的問題。它可能是 JVM 堆不足、內存泄漏、或者本地/直接內存耗盡引起的。本文用通俗的語言解釋 OOM 的常見類型、如何快速定位(命令與工具)、以及 2 個可運行的 Demo(一個“瞬間分配大對象”觸發 OOM,一個“內存泄漏”模擬)來復現和驗證問題,并給出實際修復建議與最佳實踐。

先把癥狀搞清楚 — OOM 常見表現

當程序遇到 OOM,常見異常信息有:

  • java.lang.OutOfMemoryError: Java heap space(堆內存用盡)
  • java.lang.OutOfMemoryError: GC overhead limit exceeded(GC 占比過高)
  • java.lang.OutOfMemoryError: Metaspace(元空間/類元數據用盡)
  • java.lang.OutOfMemoryError: Direct buffer memory(直接內存 / native buffer 用盡)
  • 有時伴隨未生成堆轉儲(如果沒開 -XX:+HeapDumpOnOutOfMemoryError

出現 OOM 時 JVM 往往會打印堆棧并退出。定位問題的第一步是判斷是哪種 OOM(heap / metaspace / direct / native)。

簡單定位思路(快速排查步驟)

  1. 確認 OOM 類型:查看異常消息(heap / metaspace / direct 等)。
  2. 復現場景:能否用小堆內存復現(-Xmx64m)?如果可以,說明問題容易觸發。
  3. 抓堆快照(Heap Dump):在運行時或 OOM 時生成 hprof(參數:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.hprof)。
  4. 查看類實例分布jcmd <pid> GC.class_histogram > histo.txtjmap -histo:live <pid>
  5. 用可視化工具分析:jvisualvm、Eclipse MAT(Memory Analyzer)打開 heap.hprof 找頂級占用對象和 GC Roots。
  6. 考慮 GC / 參數問題:有時候是堆太小,簡單增大 -Xmx 就能緩解,但這只是治標。要找出為何占用如此多。

瞬間分配大對象導致 OOM

這個 Demo 用來展示“把很大的數組一次性分配”導致 OOM 的情形,方便你通過減小堆內存復現并觀察。

源碼 OOMAllocate.java

// 保存為 OOMAllocate.java
public class OOMAllocate {public static void main(String[] args) throws InterruptedException {System.out.println("PID: " + ProcessHandle.current().pid());// 等待幾秒,方便 attach 工具(jvisualvm)Thread.sleep(5000);try {// 分配一個巨大的對象,觸發 OOMint size = 200_000_000; // 2e8 -> 大約 800MB for int[]System.out.println("Allocating int[" + size + "]");int[] arr = new int[size];System.out.println("Allocated: " + arr.length);} catch (Throwable t) {t.printStackTrace();}// 保持進程不退出,便于分析Thread.sleep(60_000);}
}

編譯與運行(在終端)

# 編譯
javac OOMAllocate.java# 運行:限制堆為 128MB 并在 OOM 時生成 heap dump
java -Xmx128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap_OOMAllocate.hprof OOMAllocate

預期結果:程序會在分配 int[] 時拋出 OutOfMemoryError: Java heap space,并在當前目錄生成 heap_OOMAllocate.hprof

分析

  • 這個 Demo 說明“瞬時大對象分配”在堆較小時非常容易觸發 OOM。
  • 觀察堆直方圖(jmap -histo)和 heap dump 可看到大對象占比。

內存泄漏模擬(靜態集合持續增長)

這個 Demo 模擬常見的內存泄漏:把對象不停放入靜態集合且不釋放(例如緩存或 List 沒有限制),最終導致堆耗盡。

源碼 LeakExample.java

// 保存為 LeakExample.java
import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class LeakExample {static class Holder {// 占大內存的 payloadprivate byte[] payload;public Holder(int mb) {this.payload = new byte[mb * 1024 * 1024];}}// 靜態 List 模擬緩存/泄漏private static final List<Holder> leakingList = new ArrayList<>();public static void main(String[] args) throws Exception {System.out.println("PID: " + ProcessHandle.current().pid());int mb = 1;if (args.length > 0) {mb = Integer.parseInt(args[0]);}int count = 0;try {while (true) {leakingList.add(new Holder(mb)); // 每次分配 mb MB 并保留引用count++;if (count % 10 == 0) {System.out.println("Allocated blocks: " + count + ", total approx MB: " + (count * mb));}Thread.sleep(200);}} catch (OutOfMemoryError oom) {oom.printStackTrace();System.out.println("OOM after allocating blocks: " + count);// 觸發堆轉儲如果配置了 -XX:+HeapDumpOnOutOfMemoryError}}
}

編譯與運行

javac LeakExample.java# 用較小堆觸發 OOM,如 64MB
java -Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap_LeakExample.hprof LeakExample 1

程序會持續分配 1MB 塊并保存在靜態 List,最終觸發 OutOfMemoryError。生成 heap dump 后,你可以用 jvisualvm 或 Eclipse MAT 打開 heap_LeakExample.hprof

分析思路

  • jvisualvm 連接進程,查看 heap 使用趨勢;
  • jcmd <pid> GC.class_histogramjmap -histo:live <pid> 查看哪些類占用最多(很可能是 byte[]LeakExample$Holder);
  • 在 MAT 中查看 GC Roots,找到導致持有對象的路徑(通常是靜態變量)。

調試與定位工具(實用命令與說明)

生成堆轉儲(heap dump)

在運行 Java 時加入:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heap.hprof

或者在運行時觸發:

jcmd <pid> GC.heap_dump /tmp/heap.hprof

快速統計類實例(堆直方圖)

# 使用 jcmd(推薦)
jcmd <pid> GC.class_histogram > histo.txt# 或者 jmap
jmap -histo:live <pid> > histo_jmap.txt

這會列出每個類的實例數量與占用字節,幫助定位占內存最多的類。

在線分析

  • jvisualvm(JDK 自帶或獨立下載):界面化查看堆占用、線程、profiling、GC 等,能生成堆快照并查看對象占用情況。
  • Eclipse MAT (Memory Analyzer):專業的 heap.hprof 分析工具,能找出“泄漏嫌疑人”(suspects)并生成 Leak Suspects 報表。
  • Java Flight Recorder / Mission Control(JFR/JMC):更高級的運行時分析方案,適合生產場景。

GC 日志(定位 GC 問題)

  • JDK8 典型參數:

    -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/tmp/gc.log
    
  • JDK11+ 推薦:

    -Xlog:gc*:file=./gc.log:time,tags:filecount=5,filesize=10M
    

GC 日志能幫助你判斷是否是 GC 頻繁觸發(GC overhead)而非真實內存泄漏。

常見解決策略(按場景給建議)

快速應急(治標)

  • 臨時增大堆內存:在命令行加入 -Xmx(例如 -Xmx2g)。適合內存確實不足,但要謹慎,可能掩蓋泄漏。
  • 配置堆轉儲-XX:+HeapDumpOnOutOfMemoryError 實戰必備。

根本修復(治本)

  • 查找內存泄漏源頭:用 heap dump / MAT 找到被 GC Roots 持有的對象鏈,定位泄漏點。
  • 釋放不必要的引用:例如清空緩存、避免使用長生命周期的靜態集合存放臨時對象。
  • 改用弱/軟引用:例如 WeakReferenceSoftReference 或使用 WeakHashMap 來緩存可回收對象(謹慎使用)。
  • 限制緩存容量:使用 LRU(如 Guava Cache)并設置最大容量和過期策略。
  • 優先使用流式/分塊處理:處理大文件或大數據時,使用流/分段操作,避免一次性讀入內存。
  • 優化數據結構:大量小對象可改為緊湊數組或使用原始類型數組(int[] 而非 Integer[]),或使用高性能集合(Trove、fastutil)減少裝箱開銷。
  • 檢查第三方庫:有時是第三方庫(緩存/連接池)泄漏。升級或替換。

特殊類型 OOM 的處理

  • Metaspace OOM:類加載過多或動態生成類導致,解決:-XX:MaxMetaspaceSize 增大,或查找 ClassLoader 泄漏(常見于熱部署/框架反復加載)。
  • Direct memory OOM:如果使用 NIO 直接緩沖區(ByteBuffer.allocateDirect),限制由 -XX:MaxDirectMemorySize 控制。
  • Native memory OOM:JVM 之外的 native 分配(例如 JNI、第三方庫、線程棧),需使用系統工具(pmap / top / ps)和 native 專用分析工具。

實戰技巧與最佳實踐(工程化建議)

  1. 把監控放在第一位:在生產環境中用 APM 或 JMX 監控堆使用、GC 時長與 DirectMemory 使用。
  2. 把堆設置合理化:了解機器內存與 JVM 實例數量,合理設置 -Xmx-Xms,避免過度交換。
  3. 緩存策略:為緩存設置大小上限并監控命中率和內存使用。
  4. 避免不必要的全局靜態變量:很多泄漏恰恰來自“方便”但危險的靜態集合。
  5. 使用連接池/資源池:避免短生命資源頻繁創建銷毀造成內存抖動。
  6. 測試環境做壓測:用更小的堆做壓測,提前暴露內存問題(例如用 -Xmx128m 做壓力測試)。
  7. CI 中做內存回歸測試:每次依賴升級后跑內存/性能回歸,避免引入第三方內存回歸 bug。

常見問答(QA)

Q:我可以只通過增大 -Xmx 來解決所有 OOM 嗎?
A:不推薦。增大堆只是暫時緩解,內存泄漏會繼續增長,最終仍會 OOM。應結合堆分析查根因。

Q:heap.hprof 很大,如何分析?
A:用 Eclipse MAT,它能自動給出 Leak Suspects 報告,指出持有內存最多的對象和引用鏈。jvisualvm 也能打開并交互查看。

Q:如何定位 Metaspace 泄漏?
A:查看 jcmd <pid> VM.class_histo 或 jvisualvm 的 PermGen/Metaspace 圖;如果類數量一直增長,檢查 ClassLoader 泄漏,如使用了動態代理/熱部署。

Q:我在容器(Docker / K8s)里,OOM 怎么辦?
A:容器里請把容器內存和 JVM 堆配合好,避免 JVM 看到的主機內存比實際少導致 OOM。優先使用 cgroup-aware JDK(JDK10+ 更好),并監控容器級別內存使用與 OOMKilled 事件。

總結

OutOfMemoryError 是開發/運維常見問題,定位邏輯分為“確認類型 → 生成/抓取堆快照 → 分析占用對象 → 修復”(釋放引用 / 優化內存 / 合理設置 JVM 參數)。本文提供了兩套可運行 Demo(瞬時大對象 & 內存泄漏),并給出了常用命令(jmapjcmdjvisualvm、heap dump)與修復策略。遇到 OOM,別慌,按步驟分析,定位到持有對象和引用鏈,往往就能找到根因并修復。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98036.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98036.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98036.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

阿里云服務器配置ssl-docker nginx

# 切換到您當前的目錄 cd /AAAAAAAAAAAA# 創建存放nginx配置、證書和日志的目錄結構 mkdir -p nginx-config/conf.d nginx-ssl nginx-logs# 為掛載做準備&#xff0c;您可能需要將當前dist目錄內容移動到新的html目錄 # 首先查看當前dist目錄的內容 ls -la dist/# 如果html目錄…

2025全球生成式引擎優化(GEO)服務商發展趨勢與企業賦能白皮書

引言&#xff1a;人工智能技術的迅猛發展&#xff0c;特別是在生成式AI領域的突破&#xff0c;正以前所未有的力量重塑商業世界的競爭格局。對于尋求提升在線可見性、優化品牌互動及實現可持續增長的企業而言&#xff0c;生成式引擎優化&#xff08;GEO&#xff09;已然成為數字…

海康威視工業相機SDK開發實戰:使用C/C++實現軟件觸發圖像采集(含詳細中文注釋代碼)

一、前言 在機器視覺、自動化檢測、智能制造等領域&#xff0c;工業相機是獲取圖像數據的核心設備。海康威視作為國內領先的機器視覺廠商&#xff0c;其工業相機產品線豐富&#xff0c;廣泛應用于各類工業場景。 本文將帶你從零開始&#xff0c;使用 海康MVS SDK&#xff08;Ma…

Modbus RTU 協議介紹

Modbus RTU 協議介紹 異步串行傳輸方式&#xff0c;采用二進制格式&#xff0c;適用于串行通訊&#xff08;如RS-485&#xff09;&#xff0c;效率高&#xff0c;是工業現場的主流選擇。 主站是Master&#xff0c;從站是Slave。 Modbus RTU 協議格式 幀結構 地址碼&#xf…

TCP/IP函數——sendmsg

sendmsg() 是 POSIX 標準中一個高級套接字發送函數,屬于系統調用(由操作系統內核實現),定義在 <sys/socket.h> 頭文件中。它的核心特點是支持復雜消息結構,不僅能發送常規數據,還能附加控制信息(如輔助數據、IP 選項等),適用于 TCP、UDP 等多種協議,功能比 sen…

運動控制中的插值運動(插補運動):原理、實現與應用

在自動化設備中,從起點到終點的精準軌跡控制是核心需求。當目標軌跡是直線、圓弧或復雜曲線時,僅通過離散的目標點無法實現平滑運動,這就需要插值運動(Interpolation Motion)技術 —— 通過控制算法在已知路徑點之間計算出連續的中間點,使運動部件沿預定軌跡平滑移動。本…

GMT——用于人形全身控制的通用運動跟蹤:兩階段師生訓練框架下,全身基于單一策略,且自適應采樣、MoE架構

前言 如此文《KungfuBot——基于物理約束和自適應運動追蹤的人形全身控制PBHC&#xff0c;用于學習打拳或跳舞(即RL下的動作模仿和運控)》的開頭所說 如此&#xff0c;便關注到最新出來的三個工作 第一個是GMT: General Motion Tracking for Humanoid Whole-Body Control第二個…

matlab版本粒子群算法(PSO)在路徑規劃中的應用

基于粒子群優化&#xff08;PSO&#xff09;算法的路徑規劃 MATLAB代碼實現 1. 初始化環境和參數 % 初始化環境參數 mapSize [10, 10]; % 地圖大小 startPoint [1, 1]; % 起點 endPoint [9, 9]; % 終點 obstacles [3, 3; 5, 5; 7, 7]; % 障礙物位置% PSO參數 numParticles …

Go語言面試:傳值與傳引用的區別及選擇指南

在Go語言中&#xff0c;函數參數的傳遞方式有兩種&#xff1a;傳值&#xff08;pass-by-value&#xff09;和傳引用&#xff08;pass-by-reference&#xff09;。理解這兩種方式的區別及其適用場景&#xff0c;是成為Go語言開發高手的必備技能。本文將深入探討Go語言中傳值與傳…

數據無言,網關有聲 耐達訊自動化RS485轉Profinet讓千年液位數據“開口說話”

在能源行業的數字化轉型浪潮中&#xff0c;你是否曾面臨這樣的困境&#xff1a; 現場大量采用RS485接口的液位計&#xff0c;數據孤立如信息孤島&#xff0c;無法接入Profinet高速網絡&#xff1f; 模擬信號傳輸距離受限&#xff0c;抗干擾能力弱&#xff0c;導致液位測量誤差…

出口退稅新政大提速:企業如何抓住政策紅利,提升最高13%純利?

近年來&#xff0c;出口退稅政策的優化與升級&#xff0c;正在成為外貿企業提升資金周轉率和利潤率的關鍵。國家稅務總局發布的 2022年第9號公告&#xff08;簡稱“9號公告”&#xff09;落地執行已兩年&#xff0c;外貿行業普遍感受到退稅速度顯著加快&#xff0c;平均退稅周期…

使用pytorch創建/訓練/推理OCR模型

一、任務描述 從手寫數字圖像中自動識別出對應的數字&#xff08;0-9&#xff09;” 的問題&#xff0c;屬于單標簽圖像分類任務&#xff08;每張圖像僅對應一個類別&#xff0c;即 0-9 中的一個數字&#xff09; 1、任務的核心定義&#xff1a;輸入與輸出 輸入&#xff1a;28…

新啟航開啟深孔測量新紀元:激光頻率梳技術攻克光學遮擋,達 130mm 深度 2μm 精度

摘要&#xff1a;本文聚焦于深孔測量領域&#xff0c;介紹了一種創新的激光頻率梳技術。該技術成功攻克傳統測量中的光學遮擋難題&#xff0c;在深孔測量深度達 130mm 時&#xff0c;可實現 2μm 的高精度測量&#xff0c;為深孔測量開啟了新的發展篇章。關鍵詞&#xff1a;激光…

GEO優化推薦:AI搜索新紀元下的品牌內容權威構建

引言&#xff1a;AI搜索引擎崛起與GEO策略的戰略重心轉移2025年&#xff0c;以ChatGPT、百度文心一言、DeepSeek為代表的AI搜索引擎已深入成為公眾信息獲取的核心渠道。這標志著品牌營銷策略的重心&#xff0c;正從傳統的搜索引擎優化&#xff08;SEO&#xff09;加速向生成式引…

uniapp的上拉加載H5和小程序

小程序配置{"path": "list/course-list","style": {"navigationBarTitleText": "課程列表","enablePullDownRefresh": true,"onReachBottomDistance": 150}}上拉拉觸底鉤子onReachBottom() {var that …

【和春筍一起學C++】(四十)抽象數據類型

抽象數據類型&#xff08;abstract data type, ADT&#xff09;以通用的方式描述數據類型。C中類的概念非常適合于ADT方法。例如&#xff0c;C程序通過堆棧來管理自動變量&#xff0c;堆棧可由對它執行的操作來描述。可創建空堆棧&#xff1b;可將數據項添加到堆頂&#xff08;…

大文件斷點續傳解決方案:基于Vue 2與Spring Boot的完整實現

大文件斷點續傳解決方案:基于Vue 2與Spring Boot的完整實現 在現代Web應用中,大文件上傳是一個常見但具有挑戰性的需求。傳統的文件上傳方式在面對網絡不穩定、大文件傳輸時往往表現不佳。本文將詳細介紹如何實現一個支持斷點續傳的大文件上傳功能,結合Vue 2前端和Spring Bo…

LeNet-5:手寫數字識別經典CNN

配套講解視頻&#xff0c;點擊下方名片獲取20 世紀 90 年代&#xff0c;計算機已經能識別文本&#xff0c;但圖片識別很困難。比如銀行支票的手寫數字識別&#xff0c;傳統方法需要人工設計規則&#xff0c;費時費力且精度不高。 于是&#xff0c;Yann LeCun 及其團隊提出了 Le…

如何在 C# 中將文本轉換為 Word 以及將 Word 轉換為文本

在現代軟件開發中&#xff0c;處理文檔內容是一個非常常見的需求。無論是生成報告、存儲日志&#xff0c;還是處理用戶輸入&#xff0c;開發者都可能需要在純文本與 Word 文檔之間進行轉換。有時需要將文本轉換為 Word&#xff0c;以便生成結構化的 .docx 文件&#xff0c;使內…

Open SWE:重構代碼協作的智能范式——從規劃到PR的全流程自動化革命

在軟件開發的演進史上,工具鏈的每一次革新都深刻重塑著開發者的工作方式。LangChain AI推出的Open SWE,作為首個開源的異步編程代理,正在重新定義代碼協作的邊界——它不再僅僅是代碼生成工具,而是構建了從代碼庫分析、方案規劃、代碼實現到拉取請求創建的端到端自動化工作…