JVM——記憶體模型(二):虛擬機器棧與本地方法棧
本篇文章將繼續認識Java虛擬機器中的記憶體模型,今天要認識的是我們常說的"棧”。
棧其實也分兩種,一種是虛擬機器棧,一種是本地方法棧。而我們平常說的最多的,就是虛擬機器棧。接下來就讓我們走進這兩個棧,看看他們是個啥。
1.虛擬機器棧
虛擬機器棧,即Java Virtual Machine Stacks,描述的是Java方法執行的記憶體模型。
每個方法在執行的同時都會建立一個棧幀,即Stack Frame,用於儲存區域性變量表、運算元棧、動態連線、方法出口等資訊。從這裡我們也可以知道,每一個方法從呼叫到執行完成的過程,就對應這一個棧幀在虛擬機器棧中入棧到出棧的過程
方法呼叫時,建立棧幀,並壓入虛擬機器棧;方法執行完畢,棧幀出棧並被銷燬,其過程如下圖所示:
在平常的學習交流中,我們可以發現,可多人將Java的記憶體區分為堆記憶體(Heap)和棧記憶體(Stack)(顯然,這種分法非常粗糙,從上圖我們就可以看出,Java的記憶體區要比這複雜得多。 ),這裡面說的棧記憶體,就是咱們現在正在瞭解的虛擬機器棧,或者說是虛擬機器棧中的區域性變量表部分。
2.區域性變量表
從上文我們可以知道,棧幀中存放著區域性變量表、運算元棧、動態連結和方法出口等。這裡我們重點認識一下區域性變量表。
區域性變量表存放了編譯期可知的各種資料型別,都有哪些呢?
- 各種基本資料型別,即boolean、byte、char、short、int、float、long、double,以及物件引用
- 物件引用,即reference型別,它不等同於物件本身,可能是一個指向物件起始地址的引用指標,也可能是指向一個代表物件的控制代碼或者其他與此物件相關的地址。
- returnAddress型別,指向了一條位元組碼指令的地址。
需要注意的是,64位長度的long和double型別的資料會佔用兩個區域性變數空間(Slot),其餘的資料型別只佔用一個。
那麼區域性變量表的記憶體空間在什麼時候分配呢?
上文說到,區域性變量表存放了編譯期可知的各種資料型別,因此區域性變量表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀分配多大的區域性變數空間是完全確定的,而在方法執行期間也不會改變區域性變量表的大小
3.虛擬機器棧的特點
與程式計數器一樣,虛擬機器棧是執行緒隔離的,即每個執行緒都有自己獨立的虛擬機器棧。
4.虛擬機器棧的異常狀況
在Java虛擬機器規範中,對Java虛擬機器棧規定了兩種異常的狀況:
- 如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常;
- 如果虛擬機器棧可以動態擴充套件,如果擴充套件時無法申請到足夠的記憶體,將丟擲OutOfMemoryError異常。
4.1 StackOverflowError
JVM會為每個執行緒的虛擬機器棧分配一定的記憶體大小(-Xss引數),因此虛擬機器棧能夠容納的棧幀數量是有限的,若棧幀不斷進棧而不出棧,最終會導致當前執行緒虛擬機器棧的記憶體空間耗盡,典型如一個無結束條件的遞迴函式呼叫,程式碼見下:
package javatest;
/**
* java棧溢位StackOverFlowError
*
* Created by chenjunyi on 2018/4/25.
*/
public class JvmStackoverflowtest {
private int stackLength = -1;
//通過遞迴呼叫造成StackOverFlowError
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JvmStackoverflowtest Searchin = new JvmStackoverflowtest();
try {
Searchin.stackLeak();
} catch (Throwable e) {
System.out.println("Stack length:" + Searchin.stackLength);
e.printStackTrace();
}
}
}
程式執行結果如下:
4.2 OutOfMemoryError
不同於StackOverflowError,OutOfMemoryError指的是當整個虛擬機器棧記憶體耗盡,並且無法再申請到新的記憶體時丟擲的異常。
JVM未提供設定整個虛擬機器棧佔用記憶體的配置引數。虛擬機器棧的最大記憶體大致上等於“JVM程序能佔用的最大記憶體(依賴於具體作業系統) - 最大堆記憶體 - 最大方法區記憶體 - 程式計數器記憶體(可以忽略不計) - JVM程序本身消耗記憶體”。當虛擬機器棧能夠使用的最大記憶體被耗盡後,便會丟擲OutOfMemoryError。
可以通過不斷開啟新的執行緒來模擬這種異常,程式碼如下:
**
* java棧溢位OutOfMemoryError
* JVM引數:-Xss2m
* Created by chenjunyi on 2018/4/25.
*/
public class JavaVMStackOOM {
private void dontStop() {
while (true) {
}
}
//通過不斷的建立新的執行緒使Stack記憶體耗盡
public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(() -> dontStop());
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM oom = new _03_JavaVMStackOOM();
oom.stackLeakByThread();
}
}
設定單個執行緒虛擬機器棧的佔用記憶體為2m並不斷生成新的執行緒,最終虛擬機器棧無法申請到新的記憶體,丟擲異常:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
注:這個例子容易玩爆記憶體,不要輕易嘗試,知道有這個東西就行。
5.本地方法棧(Native Method Stack)
本地方法棧的功能和特點類似於虛擬機器棧,均具有執行緒隔離的特點以及都能丟擲StackOverflowError和OutOfMemoryError異常。
他們之間的區別不過是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼),而本地方法為虛擬機器使用到的Native方法服務。
在虛擬機器規範中對於本地方法棧中方法使用的語言、使用的方式與資料結構並沒有強制規定,因此具體的虛擬機器可以自由實現它。甚至有的虛擬機器(譬如Sun HotSpot虛擬機器,即我們現在大多數人用的這個虛擬機器)直接就把本地方法棧與虛擬機器棧合二為一啦。與虛擬機器棧一樣,本地方法棧區域也會丟擲StackOverflowError和OutOfMemoryError異常。
好啦,以上就是關於虛擬機器棧與本地方法棧的相關知識總結啦,如果大家有什麼不明白的地方或者發現文中有描述不好的地方,歡迎大家留言評論,我們一起學習呀。
Biu~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~pia!