1. 程式人生 > >Java虛擬機器OOM之虛擬機器棧和本地方法棧溢位(4)

Java虛擬機器OOM之虛擬機器棧和本地方法棧溢位(4)

一、在 Java 虛擬機器規範中,對虛擬機器棧這個區域規定了兩種異常狀況:

(1)如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError 異常;
(2)如果虛擬機器棧可以動態擴充套件(當前大部分的 Java 虛擬機器都可動態擴充套件,只不過 Java 虛擬機器規範中也允許固定長度的虛擬機器棧),當擴充套件時無法申請到足夠的記憶體時會丟擲 OutOfMemoryError 異常。
(3)與虛擬機器棧一樣,本地方法棧區域也會丟擲 StackOverflowError 和OutOfMemoryError 異常。

二、前一篇文章中的第一張圖也看出來了對於虛擬機器棧和本地方法棧

如果在多執行緒的情況下是可能出現OOM的,下邊就是一個單執行緒的案例。演示一下:

/**
 * 設定  VM Args: -Xss128k
 * @author xuliugen
 *
 */
public class JavaVMStackSOF {
    private int stackLength = 1;

    //反覆呼叫
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws
Throwable { JavaVMStackSOF oom = new JavaVMStackSOF(); try { oom.stackLeak(); } catch (Throwable e) { System.out.println("stack length:" + oom.stackLength); throw e; } } }

要設定VM Args: -Xss128k(上一篇已經說到:設定棧為128k),結果如下:

stack length:40550
Exception in thread "main" java.lang.StackOverflowError at com.lc.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8) 後續很多異常省略。。。

結果表明:在單個執行緒下,無論是由於棧幀太大,還是虛擬機器棧容量太小,當記憶體無法分配的時候,虛擬機器丟擲的都是 StackOverflowError 異常,而不是OOM。

三、如果測試時不限於單執行緒,通過不斷地建立執行緒的方式倒是可以產生記憶體溢位異常。自己可以建立多個執行緒,進行測試,但是:由於在Windows 平臺的虛擬機器中, Java 的執行緒是對映到作業系統的核心執行緒上的,所以多執行緒程式碼執行時有較大的風險,可能會導致作業系統假死。所以。。。

不過結果肯定是,多執行緒的情況下,是會出現OOM的!

原理如下:

為什嗎多執行緒的情況下就會產生OOM,這樣產生的記憶體溢位異常與棧空間是否足夠大並不存在任何聯絡,或者準確地說,在這種情況下,給每個執行緒的棧分配的記憶體越大,反而越容易產生記憶體溢位異常。

原因其實不難理解,作業系統分配給每個程序的記憶體是有限制的,譬如 32 位的 Windows 限制為 2GB。虛擬機器提供了引數來控制 Java 堆和方法區的這兩部分記憶體的最大值。剩餘的記憶體為 2GB(作業系統限制)減去 Xmx(最大堆容量),再減去 MaxPermSize(最大方法區容量),程式計數器消耗記憶體很小,可以忽略掉。如果虛擬機器程序本身耗費的記憶體不計算在內,剩下的記憶體就由虛擬機器棧和本地方法棧“瓜分”了。每個執行緒分配到的棧容量越大,可以建立的執行緒數量自然就越少,建立執行緒時就越容易把剩下的記憶體耗盡。

更簡單理解的就是:有一個作業系統為2G,我們分配給兩個執行緒,每個800M,也就還剩400M,這樣的話,有一個執行緒不夠用的話,就會在400裡邊申請,所以如果剩下的越多,出現OOM的可能性越小,如果每個分配950M,這樣就剩100M,這樣的話出現OOM的可能性就更大。如果在增加執行緒,系統對每一個執行緒非配的記憶體是一定的,所以剩下的記憶體更少,這樣的話,出現OOM的可能更大,但這都是相對而說。

遇到OOM這時候我們應該怎麼做:

如果是建立過多執行緒導致的記憶體溢位,在不能減少執行緒數或者更換 64 位虛擬機器的情況下,就只能通過減少最大堆和減少棧容量來換取更多的執行緒。如果沒有這方面的經驗,這種通過“減少記憶體”的手段來解決記憶體溢位的方式會比較難以想到。這種拆東牆補西牆的方法,還是自己意會吧。