對線面試官:淺聊一下 Java 虛擬機棧?
對于 JVM(Java 虛擬機)來說,它有兩個非常重要的區域,一個是棧(Java 虛擬機棧),另一個是堆。堆是 JVM 的存儲單位,所有的對象和數組都是存儲在此區域的;而棧是 JVM 的運行單位,它主管 Java 程序運行的。那么為什么它有這樣的魔力?它存儲的又是什么數據?接下來,我們一起來看。
1.棧定義
我們先來看棧的定義,我們這里的棧指的是 Java 虛擬機棧(Java Virtual Machine Stack)也叫做 JVM 棧,《Java虛擬機規范》對此區域的說明如下:
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
In the First Edition of The Java? Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.
This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.
The following exceptional conditions are associated with Java Virtual Machine stacks:
- If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
- If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
以上內容翻譯成中文的含義如下:
Java 虛擬機棧是線程私有的區域,它隨著線程的創建而創建。它里面保存的是局部變量表(基礎數據類型和對象引用地址)和計算過程中的中間結果。Java 虛擬機的內存不需要連續,它只有兩個操作:入棧和出棧。
Java 虛擬機棧要么大小固定,要么根據計算動態的擴展和收縮。程序員可以對 Java 虛擬機棧進行初始值的大小設置和最大值的設置。
Java 虛擬機棧出現的異常有兩種:
- 當 Java 虛擬機棧大小固定時,如果程序中的棧分配超過了最大虛擬機棧就會出現 StackOverflowError 異常。
- 如果 Java 虛擬機棧是動態擴展的,那么當內存不足時,就會引發 OutOfMemoryError 的異常。
2.棧結構
棧是線程私有的,每個線程都有自己的棧(空間),棧中的數據是以棧幀(Stack Frame)的形式存在的,線程會為每個正在執行的方法生成一個棧幀,如下圖所示:
PS:當一個新的方法被調用時,就會在棧中創建一個棧幀,當方法調用完成之后,也就意味著這個棧幀會執行出棧操作。
而棧幀中又存儲了 5 個內容:
- 局部變量表(Local Variables);
- 操作(數)棧(Operand Stack);
- 動態鏈接(Dynamic Linking);
- 方法返回地址(Return Address);
- 附加信息。
如下圖所示:
棧的整體存儲結構如下圖所示:
2.1 局部變量表
局部變量表也叫做局部變量數組或本地變量表。
局部變量表是一個數組,里面存儲的內容有:
- 方法參數;
- 方法內的局部變量,也就是方法內的基本數據類型和對象引用(Reference);
- 方法返回類型(Return Address)。
接下來我們通過類生成的字節碼來觀察一下局部變量表的內容,首先,我們先來搞一個 main 方法,具體代碼如下:
然后我們編譯類,再使用“javap -v
LocalVariablesExample.class”查看字節碼生成的內容,其中包含的本地變量表內容如下:
我們通過 JClassLib 也能觀察到局部變量表的信息,如下圖所示為局部變量表的長度:
局部變量表的詳細信息如下:
2.2 操作棧
操作棧也叫做操作數棧或表示式棧,操作數棧主要用于保存計算過程的中間結果,同時作為計算過程中變量臨時的存儲空間。
思考:為什么不把程序執行過程中的中間結果保存到局部變量表,而是保存到操作數棧中呢?
因為局部變量表是數組,而數組的長度是在其創建時就要確定,所以局部變量表在編譯器就決定內容和大小了,那么在程序執行中的這些動態中間結果,是需要新的空間來保存了,而操作數棧就可以實現此功能。
2.3 動態鏈接
動態鏈接也叫做指向運行時常量池的方法引用。
這個區域的概念和作用稍微難理解一點,在每一個棧幀內部都包含一個指向運行時常量池中該棧幀所屬方法的引用。當一個方法調用了另外的其他方法時,就是通過常量池中指向方法的符號引用來表示的,那么動態鏈接的作用就是為了將這些符號引用轉換為調用方法的直接引用。
也就是說:當一個方法調用另一個方法時,不會再創建一個被調用的方法,而是通過常量池的方法引用來調用,而這個區域存儲的就是運行時常量池的方法引用,這個區域的作用就是將運行時常量池的符號引用轉換成直接引用。
2.4 方法返回地址
方法返回地址也叫做方法正常退出或異常退出的定義。
方法返回地址存放的是調用該方法的程序計數器的值。程序計數器里面保存的是該線程要執行的下一行指令的位置。
也就是說:在一個方法中調用了另一個方法,當被調用的方法執行完之后,要執行的下一行指令就是保存在此區域的。
2.5 附加信息
此區域在很多教程上會被省略,因為此區域有可能有數據,也有可能沒有數據。這些附加信息是和 Java 虛擬機實現相關的一些信息。例如,對程序調試提供支持的信息。
總結
棧作為 Java 虛擬機中最核心的組成部分之一,它包含了以下 5 部分的內容:
- 局部變量表(Local Variables):主要存儲的是方法內的基本數據類型和對象引用;
- 操作(數)棧(Operand Stack):主要用于保存計算過程的中間結果,同時作為計算過程中變量臨時的存儲空間;
- 動態鏈接(Dynamic Linking):存放的是指向運行時常量池的方法引用;
- 方法返回地址(Return Address):存放的是調用該方法的程序計數器的值;
- 一些附加信息:存儲了一些和 Java 虛擬相關的數據,比如程序的調試數據。
參考 & 鳴謝
《阿里巴巴Java開發手冊》
《尚硅谷JVM》