更新時間:2020-10-10 17:29:38 來源:動力節(jié)點 瀏覽1296次
由于在HotSpot虛擬機(jī)中并不區(qū)分JVM棧和本地方法棧,因此,對于HotSpot來說,雖然-Xoss參數(shù)(設(shè)置本地方法棧大小)存在,但實際上是無效的,棧容量只由-Xss參數(shù)設(shè)定。下面我們來通過實例探究一下JVM棧溢出的情況。
關(guān)于虛擬機(jī)棧和本地方法棧,在Java虛擬機(jī)規(guī)范中描述了兩種異常:
如果線程請求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常。
如果虛擬機(jī)在擴(kuò)展棧時無法申請到足夠的內(nèi)存空間,則拋出OutOfMemoryError異常。
這里把異常分成兩種情況,看似更加嚴(yán)謹(jǐn),但卻存在著一些互相重疊的地方:當(dāng)棧空間無法繼續(xù)分配時,到底是內(nèi)存太小,還是已使用的棧空間太大,其本質(zhì)上只是對同一件事情的兩種描述而已。
使用-Xss參數(shù)減少棧內(nèi)存容量。結(jié)果:拋出StackOverflowError異常,異常出現(xiàn)時輸出的堆棧深度相應(yīng)縮小。
定義了大量的本地變量,增大此方法幀中本地變量表的長度。結(jié)果:拋出StackOverflowError異常時輸出的堆棧深度相應(yīng)縮小。
代碼如下:
/**
* VM Args:-Xss128k
* @author zzm
*/
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;
}
}
}
運行結(jié)果:
stack length :2402
Exception in thread"main"java.lang.StackOverflowError
at org.fenixsoft.oom.VMStackSOF.leak (WIStackSOF.java :20 ) at org.fenixsoft.oom.VMStackSOF.leak (WIStackSOF.java :21 ) at org.fenixsoft.oom.VMStackSOF.leak (WIStackSOF.iava :21 )
.....后續(xù)異常堆棧信息省略
實驗結(jié)果表明:在單個線程下,無論是由于棧幀太大還是虛擬機(jī)棧容量太小,當(dāng)內(nèi)存無法分配的時候,虛擬機(jī)拋出的都是StackOverflowError異常。
如果測試時不限于單線程,通過不斷地建立線程的方式倒是可以產(chǎn)生內(nèi)存溢出異常。但是這樣產(chǎn)生的內(nèi)存溢出異常與棧空間是否足夠大并不存在任何聯(lián)系,或者準(zhǔn)確地說,在這種情況下,為每個線程的棧分配的內(nèi)存越大,反而越容易產(chǎn)生內(nèi)存溢出異常。
其實原因不難理解,操作系統(tǒng)分配給每個進(jìn)程的內(nèi)存是有限制的,譬如32位的Windows限制為2GB。虛擬機(jī)提供了參數(shù)來控制Java堆和方法區(qū)的這兩部分內(nèi)存的最大值。剩余的內(nèi)存為2GB(操作系統(tǒng)限制)減去Xmx(最大堆容量),再減去MaxPermSize(最大方法區(qū)容量),程序計數(shù)器消耗內(nèi)存很小,可以忽略掉。如果虛擬機(jī)進(jìn)程本身耗費的內(nèi)存不計算在內(nèi),剩下的內(nèi)存就由虛擬機(jī)棧和本地方法棧“瓜分”了。每個線程分配到的棧容量越大,可以建立的線程數(shù)量自然就越少,建立線程時就越容易把剩下的內(nèi)存耗盡。
這一點讀者需要在開發(fā)多線程的應(yīng)用時特別注意,出現(xiàn)StackOverflowError異常時有錯誤堆棧可以閱讀,相對來說,比較容易找到問題的所在。而且,如果使用虛擬機(jī)默認(rèn)參數(shù),棧深度在大多數(shù)情況下(因為每個方法壓入棧的幀大小并不是一樣的,所以只能說在大多數(shù)情況下)達(dá)到1000~2000完全沒有問題,對于正常的方法調(diào)用(包括遞歸),這個深度應(yīng)該完全夠用了。但是,如果是建立過多線程導(dǎo)致的內(nèi)存溢出,在不能減少線程數(shù)或者更換64位虛擬機(jī)的情況下,就只能通過減少最大堆和減少棧容量來換取更多的線程。如果沒有這方面的處理經(jīng)驗,這種通過“減少內(nèi)存”的手段來解決內(nèi)存溢出的方式會比較難以想到。
代碼如下:創(chuàng)建線程導(dǎo)致內(nèi)存溢出異常
/**
* VM Args:-Xss2M (這時候不妨設(shè)大些)
* @author zzm
*/
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) throws Throwable {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
注意,特別提示一下,如果要嘗試運行上面這段代碼,記得要先保存當(dāng)前的工作。由于在Windows平臺的虛擬機(jī)中,Java的線程是映射到操作系統(tǒng)的內(nèi)核線程上的,因此上述代碼執(zhí)行時有較大的風(fēng)險,可能會導(dǎo)致操作系統(tǒng)假死。
運行結(jié)果:
Exception in thread"main"java.lang.OutOfMemoryError :unable to create new native thread
以上就是我們對JVM棧溢出的探究過程,想要了解JVM棧溢出背后涉及到的更多的知識,可以觀看本站的Java零基礎(chǔ)教程,全面提升自己的Java基礎(chǔ)。
0基礎(chǔ) 0學(xué)費 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請后,顧問老師會電話與您溝通安排學(xué)習(xí)