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)不會在主程序運行期對PermGen space進行清理,所以如果你的應用中有很多CLASS的話,就很可能出現PermGen space錯誤,這種錯誤常見在web服務器對JSP進行pre compile的時候。如果你的WEB APP下都用了大量的第三方jar, 其大小超過了jvm默認的大小那麼就會產生此錯誤信息了。由於JVM在默認的情況下,Perm默認爲64M,而很多程序需要大量的Perm區 存,尤其使用到像spring等框架的時候,由於需要使用到動態生成類,而這些類不能被GC自動釋放,所以導致OutOfMemoryError: PermGen space異常。解決方法很簡單,增大JVM的 -XX:MaxPermSize 啓動參數,就可以解決這個問題,如過使用的是默認變量通常是64M,改成128M就可以了,-XX:MaxPermSize=128m。如果已經是128m,就改成 256m。我一般在服務器上爲安全起見,改成256m。

解決參考

調高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操作次數和數量;限制網絡的使用等等。


參考資料:

http://hi.baidu.com/wzgws/item/7f143880b5e45d5d26ebd97b

http://icyfenix.iteye.com/blog/802573


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章