天天看點

Java解決記憶體溢出問題

作者:程式員的秃頭之路

Java 堆空間

造成原因:

  • 無法在 Java 堆中配置設定對象
  • 吞吐量增加
  • 應用程式無意中儲存了對象引用,導緻對象無法被 GC 回收
  • 應用程式過度使用 finalizer。finalizer 對象不能被 GC 立刻回收。finalizer 由結束隊列服務的守護線程調用,有時 finalizer 線程的處理能力無法跟上結束隊列的增長

解決方案:

  • 機關對應:GB -> G, g;MB -> M, m;KB -> K, k
  • 使用 -Xmx 增加堆大小。例如,java -Xmx2g 表示配置設定 2 GB 的最大堆空間
  • 修複應用程式中的記憶體洩漏問題。可以使用記憶體分析工具(如 NetBeans、jvisualvm、Eclipse MAT 等)來檢測和定位記憶體洩漏的源頭
  • 減少或避免使用 finalizer。如果必須使用 finalizer,應該盡量減少 finalizer 對象的數量和執行時間,并及時調用 System.runFinalization() 來清理結束隊列

GC 開銷超過限制

造成原因:

  • Java 程序 98% 的時間在進行垃圾回收,僅恢複不到 2% 的堆空間。最後連續 5 次(編譯時常量)垃圾回收仍然如此。

解決方案:

  • 使用 -Xmx 增加堆大小。例如,java -Xmx2g 表示配置設定 2 GB 的最大堆空間
  • 使用 -XX:-UseGCOverheadLimit 取消 GC 開銷限制。這樣可以避免 JVM 抛出該錯誤,但可能會導緻應用程式變得非常緩慢或者出現其他類型的記憶體錯誤
  • 修複應用程式中的記憶體洩漏問題。可以使用記憶體分析工具(如 NetBeans、jvisualvm、Eclipse MAT 等)來檢測和定位記憶體洩漏的源頭

請求的數組大小超過虛拟機限制

造成原因:

  • 應用程式試圖配置設定一個超過堆大小的數組。
  • 數組的索引是int類型,最大值約為2^31 - 1,是以理論上數組最多可以容納2,147,483,647個元素。
  • 但實際上,Java虛拟機對數組的大小有更嚴格的限制,這個限制取決于平台和實作,通常在10億到21億之間。
  • 如果應用程式請求的數組大小超過了虛拟機的限制,就會抛出java.lang.OutOfMemoryError: Requested array size exceeds VM limit異常。

解決方案:

  • 使用 -Xmx 增加堆大小,為數組配置設定更多的記憶體空間。
  • 修複應用程式中配置設定巨大數組的 bug,避免不必要的記憶體浪費。
  • 使用 ArraysSupport.MAX_ARRAY_LENGTH 常量作為數組的最大長度,以確定與所有JDK版本和實作相容。

Perm gen 空間

造成原因:

  • Perm gen 空間是一個特殊的堆空間,與主記憶體堆分開 。
  • JVM 在 Perm gen 空間中存儲加載的類的中繼資料,以及所有的靜态内容,如靜态方法、靜态變量、靜态對象的引用等 。
  • Perm gen 空間還包含位元組碼、名稱和 JIT 資訊 。
  • 在 Java 7 之前,字元串常量池也是 Perm gen 空間的一部分 。
  • Perm gen 空間的最大大小是固定的,32位 JVM 的預設值是 64 MB,64位 JVM 的預設值是 82 MB 。
  • 當 Perm gen 空間用盡時,将抛出 java.lang.OutOfMemoryError: PermGen space 異常 。
  • 這種異常通常發生在開發環境中,當建立新的類加載器時,可能導緻類加載器無法被垃圾回收,進而産生記憶體洩漏 。

解決方案:

  • 使用 -XX:MaxPermSize 增加 Perm gen 大小,為類中繼資料配置設定更多的記憶體空間 。
  • 不重新開機應用部署應用程式可能會導緻此問題。重新開機 JVM 以解決問題 。
  • 從 Java 8 開始,Perm gen 空間被 Metaspace 取代,Metaspace 是一個本地記憶體區域,可以自動增長,預設情況下沒有上限 。
  • 使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 調整 Metaspace 的初始和最大大小 。

Metaspace

造成原因:

  • 從 Java 8 開始,Perm gen 改成了 Metaspace,Metaspace 是一個本地記憶體區域,用于存儲類的中繼資料(稱為 metaspace)。
  • 如果 metaspace 耗盡,則抛出 java.lang.OutOfMemoryError: Metaspace 異常。
  • 這種異常可能發生在以下情況:
    • 應用程式加載了大量的類,導緻 metaspace 占用過多的記憶體。
    • 應用程式使用了反射或動态代理等技術,動态生成了大量的類,導緻 metaspace 溢出。
    • 應用程式存在類加載器洩漏,導緻無法回收已經解除安裝的類的中繼資料。

解決方案:

  • 通過指令行設定 -XX:MaxMetaSpaceSize 增加 metaspace 大小,為類中繼資料配置設定更多的記憶體空間。
  • 取消 -XX:maxmetaspacedize,讓 metaspace 可以自動增長,但這可能會占用更多的本地記憶體。
  • 減小 Java 堆大小,為 MetaSpace 提供更多的可用空間。
  • 為伺服器配置設定更多的記憶體。
  • 可能是應用程式 bug,修複 bug,減少不必要的類加載或者避免類加載器洩漏。

無法建立新的本地線程

原因分析:

  • 由于記憶體不足,導緻無法建立新的線程。線程在本地記憶體中建立,是以報告此錯誤意味着本地記憶體空間不足。
  • 作業系統對每個程序可以建立的線程數有一個上限。如果超過了這個上限,就會抛出異常。
  • Java 應用程式中如果建立了過多的線程,或者存線上程洩漏的問題,就可能導緻無法建立新的本地線程的錯誤。

解決方法:

  • 為計算機配置設定更多記憶體,以便為線程配置設定足夠的本地記憶體空間。
  • 減少 Java 堆空間的使用,為本地記憶體留出更多的空間。
  • 修複應用程式中的線程洩漏問題,避免不必要的線程建立或者及時回收不再使用的線程。
  • 提高作業系統級别的限制,例如使用 ulimit -a 指令,調整每個程序可以建立的最大線程數、最大虛拟記憶體、最大使用者程序數等參數。
  • 使用 -Xss 選項減小線程堆棧大小,以減少每個線程占用的本地記憶體空間。

終止程序或子程序

原因分析:

  • 核心任務:記憶體不足時的程序終止。當可用記憶體極低時,系統會自動結束某些程序。
  • 這種情況通常發生在 Java 應用程式占用了大量的記憶體,導緻作業系統無法為其他程序配置設定足夠的記憶體。
  • 當作業系統選擇要結束的程序時,會根據一定的算法計算每個程序的得分(score),得分越高的程序越有可能被結束。
  • 當作業系統結束一個 Java 程序時,該程序會抛出 java.lang.OutOfMemoryError: Kill process or sacrifice child 異常。
  • 與其他記憶體溢出(OOM)問題不同,這種情況是由作業系統而非JVM觸發的。

解決方法:

  • 為計算機配置設定更多記憶體,以便為所有程序提供足夠的記憶體空間。
  • 減少 Java 堆空間的使用,為本地記憶體留出更多的空間。
  • 優化 Java 應用程式的性能,避免記憶體洩漏或者過度消耗記憶體。
  • 調整作業系統的參數,例如使用 ulimit -a 指令,調整每個程序可以使用的最大虛拟記憶體、最大使用者程序數等參數。
  • 使用 -Xss 選項減小線程堆棧大小,以減少每個線程占用的本地記憶體空間。

出現 stack_trace_with_native_method

原因分析:

  • 發生本地方法(native method)配置設定失敗。列印出的堆棧跟蹤資訊中,最頂層的幀為本地方法。
  • 本地方法是用其他語言(如 C 或 C++)編寫的方法,通過 Java Native Interface (JNI) 與 Java 代碼互動。
  • 本地方法在本地記憶體中配置設定空間,而不是在 Java 堆中。
  • 當本地記憶體不足時,就會抛出 java.lang.OutOfMemoryError: stack_trace_with_native_method 異常。

解決方法:

  • 利用作業系統的本地工具進行診斷和排查,例如使用 ulimit -a 指令,調整每個程序可以使用的最大虛拟記憶體、最大使用者程序數等參數 。
  • 為計算機配置設定更多記憶體,以便為本地方法配置設定足夠的記憶體空間 。
  • 減少 Java 堆空間的使用,為本地記憶體留出更多的空間 。
  • 使用 -Xss 選項減小線程堆棧大小,以減少每個線程占用的本地記憶體空間 。
  • 優化或者替換使用了本地方法的代碼,避免過度消耗本地記憶體 。

繼續閱讀