Java中常見OOM的場景及解決方法
OOM for Heap (java.lang.OutOfMemoryError: Java heap space)
分析
此OOM是由於JVM中heap的最大值不滿足需要,將設定heap的最大值調高即可,引數樣例為:-Xmx2G
JVM堆的設定是指java程式執行過程中JVM可以調配使用的記憶體空間的設定.JVM在啟動的時候會自動設定Heap size的值,其初始空間(即-Xms)是實體記憶體的1/64,最大空間(-Xmx)是實體記憶體的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設定。Heap
size 的大小是Young Generation 和Tenured Generaion 之和。
提示:在JVM中如果98%的時間是用於GC且可用的Heap size 不足2%的時候將丟擲此異常資訊。
提示:Heap Size 最大不要超過可用實體記憶體的80%,一般的要將-Xms和-Xmx選項設定為相同,而-Xmn為1/4的-Xmx值。
解決參考
調高heap的最大值,即-Xmx的值調大。
OOM for StackOverflowError (Exception in thread "main" java.lang.StackOverflowError)
分析
如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常。
如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。
一般在單執行緒程式情況下無法產生OutOfMemoryError異常,使用多執行緒方式也會出現OutOfMemeoryError,因為棧是執行緒私有的,執行緒多也會方法區溢位。
解決參考
檢查程式是否有深度遞迴。
OOM for Perm (java.lang.OutOfMemoryError: PermGen space)
分析
PermGen space的全稱是Permanent Generation space,是指記憶體的永久儲存區域,這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Loader時就會被放到PermGen space中,它和存放類例項(Instance)的Heap區域不同,GC(Garbage Collection)不會在主程式執行期對
解決參考
調高Perm的最大值,即-XX:MaxPermSize的值調大。
另外,注意一點,Perm一般是在JVM啟動時載入類進來,如果是JVM執行較長一段時間而不是剛啟動後溢位的話,很有可能是由於執行時有類被動態載入進來,此時建議用CMS策略中的類解除安裝配置。
如:-XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled
OOM for GC (java.lang.OutOfMemoryError: GC overhead limit exceeded)
分析
此OOM是由於JVM在GC時,物件過多,導致記憶體溢位,建議調整GC的策略,在一定比例下開始GC而不要使用預設的策略,或者將新代和老代設定合適的大小,需要進行微調存活率。
解決參考
改變GC策略,在老代80%時就是開始GC,並且將-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)設定的更合理。
OOM for native thread created (java.lang.OutOfMemoryError: unable to create new native thread)
分析
首先我們通過下面這個測試程式來認識這個問題:
執行的環境 (有必要說明一下,不同環境會有不同的結果):32位 Windows XP,Sun JDK 1.6.0_18, eclipse 3.4
測試程式:
import java.util.concurrent.CountDownLatch;
public class TestNativeOutOfMemoryError {
public static void main(String[] args) {
for (int i = 0;; i++) {
System.out.println("i = " + i);
new Thread(new HoldThread()).start();
}
}
}
class HoldThread extends Thread {
CountDownLatch cdl = new CountDownLatch(1);
public HoldThread() {
this.setDaemon(true);
}
public void run() {
try {
cdl.await();
} catch (InterruptedException e) {
}
}
}
不指定任何JVM引數,eclipse中直接執行輸出,輸出:
i = 5602
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:597)
at TestNativeOutOfMemoryError.main(TestNativeOutOfMemoryError.java:20)
這個異常問題本質原因是我們建立了太多的執行緒,而能建立的執行緒數是有限制的,導致了異常的發生。能建立的執行緒數的具體計算公式如下:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
MaxProcessMemory 指的是一個程序的最大記憶體
JVMMemory JVM記憶體
ReservedOsMemory 保留的作業系統記憶體
ThreadStackSize 執行緒棧的大小
在java語言裡, 當你建立一個執行緒的時候,虛擬機器會在JVM記憶體建立一個Thread物件同時建立一個作業系統執行緒,而這個系統執行緒的記憶體用的不是JVMMemory,而是系統中剩下的記憶體(MaxProcessMemory - JVMMemory - ReservedOsMemory)。
結合公式和例子進行說明:
MaxProcessMemory 在32位的 windows下是 2G
JVMMemory eclipse預設啟動的程式記憶體是64M
ReservedOsMemory 一般是130M左右
ThreadStackSize 32位 JDK 1.6預設的stacksize 325K左右
公式如下:
(2*1024*1024-64*1024-130*1024)/325 = 5841
公式計算所得5841,和實踐5602基本一致(有偏差是因為ReservedOsMemory不能很精確)
依舊使用上面的測試程式,加上下面的JVM引數,測試結果如下:
ThreadStackSize JVMMemory 能建立的執行緒數
預設的325K -Xms1024m -Xmx1024m i = 2655
預設的325K -Xms1224m -Xmx1224m i = 2072
預設的325K -Xms1324m -Xmx1324m i = 1753
預設的325K -Xms1424m -Xmx1424m i = 1435
-Xss1024k -Xms1424m -Xmx1424m i = 452
解決參考
如果JVM記憶體調的過大或者可利用率小於20%,可以建議將heap及perm的最大值下調,並將執行緒棧調小,即-Xss調小,如:-Xss128k。
在JVM記憶體不能調小的前提下,將-Xss設定較小,如:-Xss:128k。
OOM for allocate huge array (Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit)
分析
此類資訊表明應用程式試圖分配一個大於堆大小的陣列。例如,如果應用程式new一個數組物件,大小為512M,但是最大堆大小為256M,因此OutOfMemoryError會丟擲,因為陣列的大小超過虛擬機器的限制。
解決參考
(1)、首先檢查heap的-Xmx是不是設定的過小
(2)、如果heap的-Xmx已經足夠大,那麼請檢查應用程式是不是存在bug,例如:應用程式可能在計算陣列的大小時,存在演算法錯誤,導致陣列的size很大,從而導致巨大的陣列被分配。
OOM for small swap (Exception in thread "main": java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space? )
分析
丟擲這類錯誤,是由於從native堆中分配記憶體失敗,並且堆記憶體可能接近耗盡。這類錯誤可能跟應用程式沒有關係,例如下面兩種原因也會導致錯誤的發生:
(1)作業系統配置了較小的交換區
(2)系統的另外一個程序正在消耗所有的記憶體
解決參考
(1)、檢查os的swap是不是沒有設定或者設定的過小
(2)、檢查是否有其他程序在消耗大量的記憶體,從而導致當前的JVM記憶體不夠分配。
注意:雖然有時<reason>部分顯示導致OOM的原因,但大多數情況下,<reason>顯示的是提示分配失敗的源模組的名稱,所以有必要檢視日誌檔案,如crash時的hs檔案。
OOM for exhausted native memory (java.lang.OutOfMemoryErr java.io.FileInputStream.readBytes(Native Method))
分析
從錯誤日誌來看,在OOM後面沒有提示引起OOM的原因,進一步檢視stack trace發現,導致OOM的原因是由Native Method的呼叫引起的,另外檢查Java heap,發現heap的使用正常,因而需要考慮問題的發生是由於Native memory被耗盡導致的。
解決參考
從根本上來說,解決此問題的方法應該通過檢測發生問題時的環境下,native memory為什麼被佔用或者說為什麼native memory越來越小,從而去解決引起Native memory減小的問題。但是如果此問題不容易分析時,可以通過以下方法或者結合起來處理。
(1)、cpu和os保證是64位的,並且jdk也換為64位的。
(2)、將java heap的-Xmx儘量調小,但是保證在不影響應用使用的前提下。
(3)、限制對native memory的消耗,比如:將thread的-Xss調小,並且限制產生大量的執行緒;限制檔案的io操作次數和數量;限制網路的使用等等。
參考資料: