JVM OutOfMemoryError原因及排查解決方案

在Java后端開發中,java.lang.OutOfMemoryError(簡稱OOM)是一個令開發者頭疼的異常。它通常意味著Java虛擬機(JVM)在嘗試分配新對象時,發現堆中沒有足夠的空間來容納該對象,或者其他內存區域耗盡。OOM不僅會導致應用程序崩潰,還會影響系統的穩定性和可用性。

一、JVM內存區域概述

在深入探討OOM之前,我們首先回顧一下JVM的運行時數據區域,因為不同區域的內存溢出對應著不同類型的OOM。

在這里插入圖片描述

JVM內存主要分為以下幾個區域:

  • 程序計數器(Program Counter Register):一塊較小的內存空間,用于存儲當前線程所執行的字節碼的行號指示器。它是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
  • Java虛擬機棧(Java Virtual Machine Stacks):每個線程私有的內存區域,用于存儲棧幀,每個棧幀包含局部變量表、操作數棧、動態鏈接、方法出口等信息。當線程請求的棧深度大于虛擬機所允許的深度時,將拋出StackOverflowError;如果虛擬機棧可以動態擴展,當擴展時無法申請到足夠的內存時,將拋出OutOfMemoryError
  • 本地方法棧(Native Method Stacks):與虛擬機棧類似,為虛擬機使用到的Native方法服務。同樣可能拋出StackOverflowErrorOutOfMemoryError
  • Java堆(Java Heap):JVM管理的最大一塊內存,被所有線程共享,用于存放對象實例和數組。這是垃圾收集器管理的主要區域。當堆中沒有內存完成實例分配,并且堆也無法再擴展時,將拋出OutOfMemoryError: Java heap space
  • 方法區(Method Area):用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。在JDK 8之前,方法區通常被稱為“永久代”(PermGen Space),在JDK 8之后,永久代被元空間(Metaspace)取代,元空間使用的是本地內存。當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError: PermGen space(JDK 7及以前)或OutOfMemoryError: Metaspace(JDK 8及以后)。

理解這些內存區域的職責,有助于我們更準確地定位OOM的發生位置和原因。

二、常見OutOfMemoryError類型及原因

OutOfMemoryError有多種類型,每種類型都對應著不同的內存區域耗盡或特定的內存問題。以下是幾種常見的OOM類型及其原因:

1. Java heap space

這是最常見也是最經典的OOM類型,表示Java堆內存不足。

常見原因:

  • 內存泄漏(Memory Leak):應用程序中存在大量對象引用未被釋放,導致垃圾回收器無法回收這些對象所占用的內存。例如,集合類對象(如ArrayListHashMap)持續添加元素但未及時清理,或者資源(如文件流、數據庫連接)未正確關閉。
  • 大對象分配:嘗試創建過大的對象,例如一個非常大的數組或集合,超出了當前堆的可用空間。即使堆內存總量足夠,如果單個對象過大,也可能導致OOM。
  • 內存溢出(Memory Overflow):代碼中存在邏輯錯誤,導致在短時間內創建了大量對象,迅速耗盡了堆內存。例如,循環中不斷創建新對象,或者遞歸調用沒有終止條件。
  • 堆內存設置過小:JVM啟動參數中設置的堆內存(-Xmx)過小,無法滿足應用程序的運行需求。
  • 不合理的緩存:應用程序使用了緩存,但緩存策略不合理,導致緩存中的對象越來越多,最終耗盡內存。

2. PermGen space(JDK 7及以前) / Metaspace (JDK 8及以后)

這兩種OOM表示方法區內存不足。

常見原因:

  • 加載大量類:應用程序加載了大量的類,例如動態生成代理類、大量使用反射、或者在Web服務器中頻繁部署和卸載應用(導致類加載器泄漏)。
  • 常量池溢出:在JDK 7之前,String.intern()方法使用不當,可能導致永久代中的字符串常量池溢出。
  • 方法區設置過小:JVM啟動參數中設置的永久代(-XX:MaxPermSize)或元空間(-XX:MaxMetaspaceSize)過小。

3. GC overhead limit exceeded

這個錯誤是JDK 6引入的一種OOM類型,表示垃圾回收器在進行大量回收工作,但效果甚微。

常見原因:

  • 頻繁GC但回收效率低:當JVM花費98%以上的時間進行垃圾回收,但回收的堆空間卻不足2%時,就會拋出此錯誤。這通常發生在應用程序的內存使用量接近堆內存上限,并且存在大量“活”對象,導致GC無法有效釋放內存。
  • 內存泄漏:與Java heap space類似,內存泄漏也可能導致GC頻繁且效率低下。
  • 堆內存設置過小:堆內存設置過小,導致GC頻繁觸發,且每次回收的內存有限。

4. unable to create new native Thread

這個錯誤表示JVM無法創建新的本地線程。

常見原因:

  • 創建大量線程:應用程序創建了過多的線程,超出了操作系統或JVM的限制。每個線程都需要占用一定的內存(包括Java棧和本地棧),過多的線程會耗盡系統內存。
  • 系統資源限制:操作系統對單個進程可創建的線程數有限制。例如,Linux系統中的/proc/sys/kernel/pid_max/proc/sys/kernel/thread-maxulimit -u等參數會影響線程創建。
  • 棧內存設置過大:通過-Xss參數設置的每個線程棧內存過大,導致在創建大量線程時迅速耗盡內存。

5. Requested array size exceeds VM limit

這個錯誤表示嘗試分配的數組大小超出了JVM的限制。

常見原因:

  • 不合理的超大數組分配:代碼中嘗試創建了一個理論上非常大的數組,其大小超出了JVM所能尋址的最大范圍。這通常是由于編程錯誤或對數據量預估不足導致的。

6. Out of swap space

這個錯誤表示操作系統層面的交換空間(swap space)不足。

常見原因:

  • 物理內存不足:應用程序或系統中的其他進程消耗了大量的物理內存,導致操作系統不得不頻繁使用交換空間,最終耗盡交換空間。
  • 交換空間設置過小:操作系統配置的交換空間大小不足以應對當前系統的內存壓力。

7. stack_trace_with_native_method

這個錯誤通常表示在本地方法(Native Method)執行過程中發生了內存分配失敗。

常見原因:

  • JNI代碼或本地庫問題:應用程序通過JNI(Java Native Interface)調用本地代碼,而本地代碼在執行過程中申請內存失敗。這通常與C/C++等本地語言編寫的庫有關,排查難度較大。

三、OutOfMemoryError排查解決方案

當應用程序發生OOM時,我們需要一套系統的排查方法來定位問題并解決它。以下是通用的排查步驟和解決方案:

1. 收集OOM信息

  • 查看錯誤日志:OOM發生時,JVM會在控制臺或日志文件中打印詳細的錯誤信息,包括OOM的類型、發生位置(堆、棧、方法區等)以及一些提示信息。這是排查問題的第一手資料。
  • 配置JVM參數生成Heap Dump:在JVM啟動參數中添加-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/path/to/heapdump.hprof,可以在OOM發生時自動生成堆內存快照(Heap Dump)文件。這個文件包含了OOM發生時堆中所有對象的信息,是分析內存泄漏和內存溢出的關鍵。
  • 配置GC日志:添加-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log等參數,可以打印詳細的GC日志。通過分析GC日志,可以了解GC的頻率、耗時、回收效果等,判斷是否存在GC問題。

2. 分析Heap Dump文件

Heap Dump文件是排查OOM最重要的工具。我們可以使用專業的內存分析工具來打開和分析它。

  • Eclipse Memory Analyzer Tool (MAT):MAT是一個功能強大的Java堆內存分析工具,可以幫助我們快速定位內存泄漏、大對象以及不合理的內存使用模式。通過MAT,我們可以:
    • 分析支配樹(Dominator Tree):找出占用內存最多的對象,通常是內存泄漏的“根源”。
    • 查找內存泄漏嫌疑(Leak Suspects):MAT會自動分析并給出內存泄漏的嫌疑報告。
    • 查看對象引用鏈:分析對象的引用關系,找出哪些對象阻止了垃圾回收。
    • 比較Heap Dump:如果能獲取到OOM發生前后的多個Heap Dump文件,可以通過比較它們來發現內存增長的趨勢和新增的大對象。
  • VisualVM:VisualVM是一個集成了多種JVM工具的圖形化工具,可以用于監控、分析和診斷Java應用程序。它也支持加載和分析Heap Dump文件,并提供實時的內存、CPU、線程等監控功能。

3. 定位和解決問題

根據OOM類型和Heap Dump分析結果,采取相應的解決方案:

3.1 針對Java heap space
  • 優化代碼,避免內存泄漏
    • 及時釋放資源:確保文件流、數據庫連接、網絡連接等資源在使用完畢后及時關閉。
    • 清理集合對象:對于長期存活的集合(如緩存、監聽器列表),定期清理不再需要的對象。
    • 弱引用/軟引用:對于緩存等場景,可以考慮使用WeakHashMapSoftReference來存儲對象,讓GC在內存不足時優先回收。
    • 避免內部類持有外部類引用:非靜態內部類會隱式持有外部類的引用,可能導致外部類無法被回收。
  • 檢查大對象分配
    • 審查代碼:檢查是否存在創建超大數組或集合的代碼,如果確實需要處理大量數據,考慮分批處理或使用流式處理。
    • 調整數據結構:選擇更節省內存的數據結構。
  • 調整JVM堆內存參數
    • 增大堆內存:根據應用程序的實際內存使用情況,適當增大-Xmx-Xms參數的值。但并非越大越好,過大的堆內存可能導致GC停頓時間過長。
    • 合理設置新生代和老年代比例:通過-XX:NewRatio-Xmn參數調整新生代大小,影響GC的頻率和效率。
3.2 針對PermGen space / Metaspace
  • 優化類加載
    • 減少不必要的類加載:避免在運行時動態生成過多不必要的類。
    • 清理Web應用:在Web服務器中,確保每次部署新版本時,舊版本的類加載器能夠完全卸載,避免類加載器泄漏。
  • 調整方法區內存參數
    • 增大永久代/元空間:適當增大-XX:MaxPermSize(JDK 7及以前)或-XX:MaxMetaspaceSize(JDK 8及以后)的值。
3.3 針對GC overhead limit exceeded
  • 優化代碼,減少對象創建:減少不必要的對象創建,復用對象,避免在循環中頻繁創建臨時對象。
  • 調整GC策略:根據應用程序的特點,選擇合適的垃圾回收器(如G1、CMS等),并調整相關參數,以優化GC性能。
  • 增大堆內存:如果GC頻繁且效率低下,可能是堆內存確實不足,適當增大堆內存可能緩解問題。
  • 禁用GC開銷限制(不推薦):通過-XX:-UseGCOverheadLimit可以禁用此限制,但這樣做只是延遲了OOM的發生,最終還是會以Java heap space的形式出現,并不能解決根本問題。
3.4 針對unable to create new native Thread
  • 減少線程創建
    • 使用線程池:合理使用線程池來管理和復用線程,避免頻繁創建和銷毀線程。
    • 檢查業務邏輯:審查代碼,看是否存在不必要的線程創建,或者線程創建后未及時關閉。
  • 調整系統參數
    • 增大操作系統線程限制:根據需要調整Linux系統中的ulimit -u/proc/sys/kernel/pid_max/proc/sys/kernel/thread-max等參數。
  • 調整JVM棧內存參數
    • 減小線程棧大小:適當減小-Xss參數的值,但要注意過小的棧可能導致StackOverflowError
3.5 針對Requested array size exceeds VM limit
  • 審查代碼:仔細檢查代碼中所有數組的創建,特別是那些大小由動態計算或用戶輸入決定的數組。確保數組大小在合理范圍內,并進行邊界檢查。
  • 分批處理或流式處理:如果需要處理的數據量確實很大,考慮將數據分批加載和處理,或者使用流式處理方式,避免一次性將所有數據加載到內存中。
3.6 針對Out of swap space
  • 增加物理內存:這是最直接有效的解決方案。
  • 增大交換空間:在操作系統層面增加交換空間的大小。
  • 檢查其他進程:查看系統上是否有其他進程占用了大量內存,如果可以,嘗試優化或遷移這些進程。
3.7 針對stack_trace_with_native_method
  • 排查本地代碼:這通常需要具備本地代碼(C/C++)的調試能力,使用操作系統提供的工具(如stracelsofgdb等)來分析本地方法的內存使用情況。
  • 更新或替換本地庫:如果問題出在第三方本地庫,嘗試更新到最新版本或尋找替代方案。

四、示例代碼與JVM參數配置

為了更好地理解和排查OOM,以下提供一些示例代碼和常用的JVM參數配置。

1. Java heap space 示例

以下是一個簡單的Java代碼示例,可能導致 java.lang.OutOfMemoryError: Java heap space

import java.util.ArrayList;
import java.util.List;public class OOMHeapSpace {public static void main(String[] args) {List<Object> list = new ArrayList<>();while (true) {list.add(new Object()); // 不斷創建新對象并添加到列表中,導致內存泄漏}}
}

運行上述代碼時,如果JVM堆內存設置較小,很快就會出現 OutOfMemoryError: Java heap space

2. JVM參數配置示例

以下是一些常用的JVM參數配置,用于OOM的排查和內存調優:

  • 設置堆內存大小

    -Xms512m -Xmx1024m
    
    • -Xms:設置JVM初始堆內存為512MB。
    • -Xmx:設置JVM最大堆內存為1024MB。
  • OOM時生成Heap Dump文件

    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/heapdump.hprof
    
    • -XX:+HeapDumpOnOutOfMemoryError:當發生OOM時,自動生成Heap Dump文件。
    • -XX:HeapDumpPath:指定Heap Dump文件的保存路徑。
  • 打印GC詳細日志

    -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/data/gc.log
    
    • -XX:+PrintGCDetails:打印詳細的GC日志。
    • -XX:+PrintGCDateStamps:在GC日志中打印時間戳。
    • -Xloggc:指定GC日志的保存路徑。
  • 設置元空間大小(JDK 8及以后)

    -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
    
    • -XX:MetaspaceSize:設置元空間初始大小為128MB。
    • -XX:MaxMetaspaceSize:設置元空間最大大小為256MB。

通過合理配置這些參數,可以幫助我們更好地監控和診斷JVM的內存問題。

總結

OutOfMemoryError是Java應用程序中常見的性能問題,但通過系統的排查方法和對JVM內存模型的深入理解,我們可以有效地定位并解決這些問題。關鍵在于:

  • 預防為主:在開發階段就注意編寫高質量的代碼,避免內存泄漏和不合理的大對象分配。
  • 監控先行:在生產環境中,持續監控JVM的內存使用情況和GC行為,及時發現潛在的內存問題。
  • 工具輔助:熟練使用jmapjstackJConsoleVisualVMMAT等工具進行問題診斷。
  • 參數調優:根據應用程序的特點和實際運行情況,合理調整JVM啟動參數,優化內存分配和垃圾回收策略。

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

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

相關文章

吐槽之前后端合作開發

大家好&#xff0c;我是佳瑞&#xff0c;從事10多年java開發程序員&#xff0c;爆照一張&#xff0c;存活互聯網。 也做過vue開發自己的網站&#xff0c;覺得前端是真比后端開發輕松很多&#xff0c;就是畫頁面調樣式&#xff0c;打包發布&#xff0c;當然不說是高級源碼修改…

Oracle LogMiner日志分析工具介紹

Oracle LogMiner日志分析工具介紹 LogMiner使用須知LogMiner字典使用online catalog作為日志挖掘字典使用redo日志文件作為日志挖掘字典使用文本文件作為日志挖掘字典Redo日志文件自動獲取日志文件手動獲取日志文件啟動LogMiner進行分析V$LOGMNR_CONTENTS視圖LogMiner使用須知 …

2-4 Dockerfile指令(個人筆記)

以下指令基于 ubuntu Dockerfile整體示例 From&#xff1a;設置基礎鏡像 Maintainer &#xff1a;鏡像維護者信息 COPY/ADD&#xff1a;添加本地文件到鏡像中 WorkDir&#xff1a;設置工作目錄 Run&#xff1a;執行命令 CMD/EntryPoint&#xff1a;配置容器啟動時執行的命令

Redis主從架構哨兵模式

文章目錄 概述一、主從搭建實例二、主從同步原理三、哨兵架構3.1、搭建哨兵架構3.2、演示故障恢復3.3、哨兵日志 概述 在生產環境下&#xff0c;Redis通常不會單機部署&#xff0c;為了保證高可用性&#xff0c;通常使用主從模式或集群架構&#xff0c;同時也面臨著一些問題&am…

基于深度學習yolov5的安全帽實時識別檢測系統

摘要&#xff1a;在現代工業和建筑行業中&#xff0c;確保員工的安全是至關重要的一環。安全帽作為一項基礎的個人防護設備&#xff0c;對于降低頭部受傷的風險發揮著關鍵作用。然而&#xff0c;確保工作人員在施工現場始終正確佩戴安全帽并非易事。傳統的人工檢查方法不僅效率…

GitLab 18.1 發布 Runner、無效的個人訪問令牌查看等功能,可升級體驗!

GitLab 是一個全球知名的一體化 DevOps 平臺&#xff0c;很多人都通過私有化部署 GitLab 來進行源代碼托管。極狐GitLab 是 GitLab 在中國的發行版&#xff0c;專門為中國程序員服務。可以一鍵式部署極狐GitLab。 學習極狐GitLab 的相關資料&#xff1a; 極狐GitLab 官網極狐…

量子計算與AI融合 - 企業級安全威脅應對

量子計算&#xff08;QC&#xff09;雖帶來萬億級市場機遇&#xff08;2025-2035年&#xff09;&#xff0c;但潛藏重大安全風險&#xff1a;可能破解現有加密系統&#xff0c;催生"現在竊取&#xff0c;未來解密"攻擊。美國NIST已啟動后量子加密標準&#xff0c;但技…

Excel:filter函數實現動態篩選的方法

filter的意思是“過濾、篩選”&#xff0c;動態篩選&#xff0c;FILTER()函數可以將對篩選區域內容&#xff0c;并將結果自動溢出生成一個新區域&#xff0c;以下是函數的使用方法&#xff1a; &#xff08;一&#xff09;情景&#xff1a;給定兩列數據&#xff0c;我需要根據…

蘭洋科技上合組織論壇發表專題分享,全球液冷布局引領綠色算力未來

2025年6月17-19日&#xff0c;中國—上海合作組織數字技術合作發展論壇在新疆克拉瑪依市舉辦。作為第四次上海合作組織成員國信息通信技術發展部門負責人會議的配套會議&#xff0c;論壇以“數字化轉型助力可持續發展&#xff0c;數字包容促進上合共同繁榮”為主題&#xff0c;…

LED-Merging: 無需訓練的模型合并框架,兼顧LLM安全和性能!!

摘要&#xff1a;對預訓練大型語言模型&#xff08;LLMs&#xff09;進行微調以適應特定任務&#xff0c;會帶來巨大的計算和數據成本。雖然模型合并提供了一種無需訓練的解決方案&#xff0c;用于整合多個特定任務的模型&#xff0c;但現有方法存在安全性與效用性之間的沖突&a…

火山引擎向量數據庫 Milvus 版正式開放

資料來源&#xff1a;火山引擎-開發者社區 隨著AI技術的不斷演進發展&#xff0c;非結構化數據也迎來了爆發式的增長。Milvus作為一款為大規模向量相似度搜索和 AI 應用開發設計的開源向量數據庫系統&#xff0c;目前已在業界占據領導地位。當前 Milvus 已經被 5,000 家企業所…

SQL SERVER存儲過程

什么是存儲過程 SQL 存儲過程&#xff08;Stored Procedure&#xff09;是一個在數據庫中預編譯并存儲的一組 SQL 語句。它們可以包含查詢、插入、更新、刪除等數據庫操作&#xff0c;甚至包括控制流語句&#xff08;如條件判斷、循環等&#xff09;。存儲過程可以通過調用來執…

Lombok注解 - 提高Java開發效率

01 繁瑣編碼 初入 Java 開發領域時&#xff0c;編寫實體類的瑣碎經歷想必各位都深有感觸。 每當創建一個實體類&#xff0c;鋪天蓋地的 getter、setter、toString 方法接踵而至&#xff0c;手指在鍵盤上頻繁敲擊&#xff0c;酸痛不已。 而 Lombok 這一神器的出現&#xff0c…

Linux修改uboot啟動延時方法詳細攻略,觸覺智能RK3568開發板演示

修改uboot延時 首先查找defconfig文件 ./build.sh uboot #通過編譯日志查看使用的defconfig文件ls u-boot/configs/*3568* #在SDK根目錄下執行該操作 如圖標注處就是所使用的u-boot配置文件。 然后修改延時數&#xff1a; vim u-boot/configs/rk3568_defconfig 將CONFIG_BOO…

dockers virbox 安裝

sudo apt remove docker docker-engine docker.io containerd runc 更新包索引并安裝依賴 sudo apt update sudo apt install ca-certificates curl gnupg 添加Docker官方GPG密鑰 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux…

Restormer: Efficient Transformer for High-Resolution Image Restoration 論文閱讀

題目 (Title): Restormer&#xff1a;用于高分辨率圖像恢復的高效Transformer 摘要 (Abstract): 由于卷積神經網絡&#xff08;CNN&#xff09;在從大規模數據中學習可泛化的圖像先驗方面表現出色&#xff0c;這些模型已被廣泛應用于圖像恢復及相關任務。最近&#xff0c;另一…

音視頻開發協議棧全景解析

音視頻開發協議棧全景解析 引言&#xff1a;協議棧的重要性與演進 在當今數字化時代&#xff0c;音視頻技術已成為互聯網基礎設施的核心組成部分。從視頻會議、直播到智能安防、元宇宙應用&#xff0c;音視頻協議棧的設計直接影響著用戶體驗質量(QoE)。作為開發者&#xff0c…

Java面試題025:一文深入了解數據庫Redis(1)

歡迎大家關注我的JAVA面試題專欄,該專欄會持續更新,從原理角度覆蓋Java知識體系的方方面面。 一文吃透JAVA知識體系(面試題)https://bl

Python:調用json.dumps處理datetime對象數據

文章目錄 前言一、查詢SQL語句中數據轉換1、思路2、示例3、常用格式化模式4、注意事項 二、自定義JSONEncoder處理1、思路2、示例3、使用方法 寫在結尾 前言 使用Python開發查詢PostgreSQL數據庫&#xff0c;返回數據中有timestamp類型數據字段。如果使用json.dumps轉換成json對…

QT6 源(130)視圖模型架構中的字符串列表模型 QStringListModel:成員函數,本類的繼承關系圖以及源碼注釋

&#xff08;1&#xff09;字符串列表型的 model &#xff0c;可以交給視圖 view 來顯示&#xff0c;也可以由組合框 comboBox 讀取其中的內容 &#xff1a; &#xff08;2&#xff09;以下開始學習本字符串 model 里的成員函數&#xff0c;本類沒有再定義信號與槽函數 &#x…