對線面試官:淺聊一下 Java 虛擬機器器棧?

2023-02-09 09:00:18

對於 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 個內容:

  1. 區域性變數表(Local Variables);
  2. 操作(數)棧(Operand Stack);
  3. 動態連結(Dynamic Linking);
  4. 方法返回地址(Return Address);
  5. 附加資訊。

如下圖所示:

棧的整體儲存結構如下圖所示:

2.1 區域性變數表

區域性變數表也叫做區域性變數陣列或本地變數表。
區域性變數表是一個陣列,裡面儲存的內容有:

  • 方法引數;
  • 方法內的區域性變數,也就是方法內的基本資料型別和物件參照(Reference);
  • 方法返回型別(Return Address)。

接下來我們通過類生成的位元組碼來觀察一下區域性變數表的內容,首先,我們先來搞一個 main 方法,具體程式碼如下:

public static void main(String[] args) {
    int num = 0;
    LocalVariablesExample lv =
            new LocalVariablesExample();
}

然後我們編譯類,再使用「javap -v LocalVariablesExample.class」檢視位元組碼生成的內容,其中包含的本地變數表內容如下:

我們通過 JClassLib 也能觀察到區域性變數表的資訊,如下圖所示為區域性變數表的長度:

區域性變數表的詳細資訊如下:

2.2 操作棧

操作棧也叫做運算元棧或表示式棧,運算元棧主要用於儲存計算過程的中間結果,同時作為計算過程中變數臨時的儲存空間。

思考:為什麼不把程式執行過程中的中間結果儲存到區域性變數表,而是儲存到運算元棧中呢?

因為區域性變數表是陣列,而陣列的長度是在其建立時就要確定,所以區域性變數表在編譯器就決定內容和大小了,那麼在程式執行中的這些動態中間結果,是需要新的空間來儲存了,而運算元棧就可以實現此功能。

2.3 動態連結

動態連結也叫做指向執行時常數池的方法參照。

這個區域的概念和作用稍微難理解一點,在每一個棧幀內部都包含一個指向執行時常數池中該棧幀所屬方法的參照。當一個方法呼叫了另外的其他方法時,就是通過常數池中指向方法的符號參照來表示的,那麼動態連結的作用就是為了將這些符號參照轉換為呼叫方法的直接參照。

也就是說:當一個方法呼叫另一個方法時,不會再建立一個被呼叫的方法,而是通過常數池的方法參照來呼叫,而這個區域儲存的就是執行時常數池的方法參照,這個區域的作用就是將執行時常數池的符號參照轉換成直接參照。

2.4 方法返回地址

方法返回地址也叫做方法正常退出或異常退出的定義。

方法返回地址存放的是呼叫該方法的程式計數器的值。程式計數器裡面儲存的是該執行緒要執行的下一行指令的位置

也就是說:在一個方法中呼叫了另一個方法,當被呼叫的方法執行完之後,要執行的下一行指令就是儲存在此區域的。

2.5 附加資訊

此區域在很多教學上會被省略,因為此區域有可能有資料,也有可能沒有資料。這些附加資訊是和 Java 虛擬機器器實現相關的一些資訊。例如,對程式偵錯提供支援的資訊。

總結

棧作為 Java 虛擬機器器中最核心的組成部分之一,它包含了以下 5 部分的內容:

  1. 區域性變數表(Local Variables):主要儲存的是方法內的基本資料型別和物件參照;
  2. 操作(數)棧(Operand Stack):主要用於儲存計算過程的中間結果,同時作為計算過程中變數臨時的儲存空間;
  3. 動態連結(Dynamic Linking):存放的是指向執行時常數池的方法參照;
  4. 方法返回地址(Return Address):存放的是呼叫該方法的程式計數器的值;
  5. 一些附加資訊:儲存了一些和 Java 虛擬相關的資料,比如程式的偵錯資料。

參考 & 鳴謝

《阿里巴巴Java開發手冊》
《尚矽谷JVM》

本文已收錄到 Gitee 開源倉庫《Java 面試指南》,其中包含的內容有:Redis、JVM、並行、並行、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設計模式、訊息佇列等模組。Java 面試有它就夠了:超全 Java 常見面試題,持續更新...