一、虛擬機(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的使用。