JVM學(xué)習(xí)筆記——虛擬機(jī)棧的溢出

一、虛擬機(jī)棧

Java虛擬機(jī)棧(Java Virtual Machine Stacks) 是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)。每個(gè)方法從被調(diào)用到完成,就對(duì)應(yīng)這一個(gè)棧幀在虛擬機(jī)棧的入棧到出棧的過程。

虛擬機(jī)棧隔離的,每個(gè)線程都有自己獨(dú)立的虛擬機(jī)棧。

在 Java 虛擬機(jī)規(guī)范中,對(duì)虛擬機(jī)棧這個(gè)區(qū)域規(guī)定了兩種異常狀況:
1. 如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出 StackOverflowError 異常;
2. 如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分的 Java 虛擬機(jī)都可動(dòng)態(tài)擴(kuò)展),在擴(kuò)展時(shí)無法申請到足夠的內(nèi)存時(shí)會(huì)拋出 OutOfMemoryError 異常。

二、虛擬機(jī)的 StackOverflowError 異常

通過-Xss參數(shù)減小棧內(nèi)存的容量,然后不斷調(diào)用方法造成棧溢出。利用第一種異常狀況拋出StackOverflowError 異常。

public class JVMStackSOF {
    private int stacklength = 1;   // 記錄棧深度

    // 調(diào)用這個(gè)遞歸方法以造成棧溢出
    public void stackPush(){
        stacklength++;
        stackPush();
    }
    
    public static void main(String[] args) throws Throwable{
        JVMStackSOF sof = new JVMStackSOF();
        try{
            sof.stackPush();
        }catch(Throwable e){
            System.out.println("stack length = " + sof.stacklength);
            throw e;
        }
    }
}

openjdk@ubuntu:~$ java -Xss256k -cp
/home/openjdk/NetBeansProjects/JavaApplication1/build/classes test_JVMStackSOF.JVMStackSOF

stack length = 1888
Exception in thread "main" java.lang.StackOverflowError
?? at test_JVMStackSOF.JVMStackSOF.stackPush(JVMStackSOF.java:17)
?? at test_JVMStackSOF.JVMStackSOF.stackPush(JVMStackSOF.java:18)
……此處省略多行調(diào)用棧信息

黑體字部分是我執(zhí)行時(shí)的指令
-Xss256K:設(shè)置參數(shù)棧內(nèi)存容量為256K

在單個(gè)線程下,虛擬機(jī)棧容量設(shè)置的很小,當(dāng)內(nèi)存無法分配的時(shí)候,虛擬機(jī)拋出 StackOverflowError 異常

三、虛擬機(jī)的 OutOfMemoryError 異常

通過-Xss2M參數(shù)增大棧內(nèi)存的容量,然后不斷開啟新的線程。利用第二種異常狀況拋出OutOfMemoryError 異常。

blic class JVMStackOOM {

    private void dontStop() {
        while (true) {
        }
    }

    public static void main(String[] args) {
        // 不斷開啟新的線程消耗虛擬機(jī)??臻g
        while (true) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            }).start();
        }
    }
}

以上代碼我嘗試在VMware上的linux平臺(tái)的虛擬機(jī)中運(yùn)行。但是似乎VMware對(duì)此有優(yōu)化,該程序可以正常運(yùn)行且不會(huì)拋出異常,經(jīng)過測試發(fā)現(xiàn)即使產(chǎn)生了1000多個(gè)線程依然沒有拋出異常。然后嘗試了在物理機(jī)的Windows平臺(tái)的虛擬機(jī)上運(yùn)行,然后就死機(jī)了?。?!orz

由于在Windows 平臺(tái)的虛擬機(jī)中,Java 的線程是映射到操作系統(tǒng)的內(nèi)核線程上的,所以多線程代碼執(zhí)行時(shí)有較大的風(fēng)險(xiǎn),可能會(huì)導(dǎo)致操作系統(tǒng)假死。
所以這個(gè)測試暫時(shí)無法實(shí)現(xiàn),電腦上裝有l(wèi)inux平臺(tái)的朋友可以試一試。

四、原理

提問:為什么在第一例中設(shè)置參數(shù)時(shí)要減小棧內(nèi)容量而第二例要增大內(nèi)存容量呢?
答:主要是因?yàn)?Xss參數(shù)設(shè)置的是一個(gè)線程的棧大小,前面已經(jīng)說過虛擬機(jī)棧是線程私有的,即每個(gè)線程都有一個(gè)自己的棧。
操作系統(tǒng)分配給每個(gè)進(jìn)程的內(nèi)存是有限制的,譬如 32 位的 Windows 限制為 2GB。虛擬機(jī)提供了參數(shù)來控制 Java 堆和方法區(qū)的這兩部分內(nèi)存的最大值。2GB(操作系統(tǒng)限制的內(nèi)存大小)減去 Xmx(最大堆容量),再減去 MaxPermSize(最大方法區(qū)容量),程序計(jì)數(shù)器消耗內(nèi)存很小,可以忽略掉。如果虛擬機(jī)進(jìn)程本身耗費(fèi)的內(nèi)存不計(jì)算在內(nèi),剩下的內(nèi)存就由虛擬機(jī)棧和本地方法?!肮戏帧绷恕?br> 所以每個(gè)線程分配到的棧容量越大,可以建立的線程數(shù)量自然就越少,建立線程時(shí)就越容易把剩下的內(nèi)存耗盡。第一例中把??臻g占滿而拋出 StackOverflowError 異常,第二例中把內(nèi)存消耗完而拋出 OutOfMemoryError 異常。

關(guān)于Jva堆溢出的相關(guān)測試在JVM學(xué)習(xí)筆記——jhat的使用,其中還包括對(duì)于jhat的使用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容