虛擬機字節碼執行引擎 —— 運行時棧幀


本文部分摘自《深入理解 Java 虛擬機》


執行引擎

執行引擎是 Java 虛擬機核心的組成部分之一,作用就是用來執行字節碼。在 Java 虛擬機規範中執行引擎只是一個概念模型,不同的虛擬機可以有不同的實現,通常會有解釋執行(通過編譯器執行)和編譯執行(通過即時編譯器產生本地代碼執行)兩種選擇,或者二者兼備。但無論是何種實現,從外觀上看,所有 Java 虛擬機的執行引擎的輸入、輸出都是一致的:輸入的是字節碼的二進制流,處理過程是解析並執行字節碼,輸出是執行結果


運行時棧幀結構

Java 虛擬機以方法作爲最基本的執行單元,棧幀則是用於支持虛擬機進行方法調用和方法執行背後的數據結構。一個方法從調用開始至執行結束的過程,都對應一個棧幀在虛擬機棧裏面從入棧到出棧的過程。

每一個棧幀到包括了局部變量表、操作數棧、動態連接、方法返回地址和一些額外的附加信息,下面將逐一做詳細介紹:

1. 局部變量表

局部變量表(Local Variables Table)是一組變量值的存儲空間,用於存放方法參數和方法內部定義的局部變量。在 Java 程序被編譯爲 Class 文件時,就在方法的 Code 屬性的 max_locals 數據項中確定了該方法所需分配的局部變量表的最大容量。

局部變量表的容量以變量槽(Variable Slot)爲最小單位,Java 虛擬機規範中並沒有明確指出一個變量槽應占用的內存空間大小。對於 boolean、byte、char、short、int、float、reference 或 returnAddress 類型的數據,都可以使用 32 位或更小的物理內存來存儲。而對於 64 位的數據類型,如 long 和 double,Java 虛擬機會以高位對齊的方式爲其分配兩個連續的變量槽空間。對於兩個相鄰的共同存放一個 64 位數據的變量槽,虛擬機不允許採用任何方式單獨訪問其中的某一個。

當一個方法被調用時,Java 虛擬機會使用局部變量表來完成參數值到參數變量列表的傳遞過程,即實參到形參的傳遞。如果執行的是實例方法,那局部變量表中第 0 位索引的變量槽默認是用於傳遞方法所屬對象實例的引用,通過關鍵字 this 來訪問這個隱含參數,其餘參數則按照參數表順序排列,佔用從 1 開始的局部變量槽。

爲了儘可能節省棧幀所耗用的內存空間,局部變量表中的變量槽是可以重用的,方法體中定義的變量,其作用域並不一定會覆蓋整個方法體,如果當前字節碼 PC 計數器的值已經超過了某個變量的作用域,那這個變量對應的變量槽就可以交給其他變量重用。不過這種設計會伴隨有額外的副作用,例如在某些情況下變量槽的複用會直接影響到系統的垃圾收集行爲

public static void main(String[] args) {
    {
        byte[] placeholder = new byte[64 * 1024 * 1024];
    }
    // int a = 0;
    System.gc();
}

當執行到 System.gc() 時,雖然已經脫離 placeholder 的作用域,但由於變量槽還存在關於 placeholder 數組對象的引用,所以不會被回收。如果我們把 int a = 0 這段註釋打開,那麼原本 placeholder 對應的變量槽就會被其他變量複用,自然也就可以回收了。有時我們會看到手動將不再使用的變量置爲 null 的操作,這並不見得是毫無意義的操作,可以將變量對應的局部變量槽清空。但在實際情況中,賦 null 值的操作在經過即時編譯優化後幾乎一定會被當成無效操作而被抹除,因此以恰當的變量作用域來控制變量的回收時間纔是最優雅的解決手段。

2. 操作數棧

操作數棧(Operation Stack)是一個後進先出的棧結構,其最大深度也在編譯時就已確定。當一個方法剛開始執行時,這個方法的操作數棧是空的。在方法的執行過程中,會有各種字節碼指令往操作數棧中寫入和提取內容,也就是入棧和出棧的操作。

3. 動態連接

每個棧幀都包含一個指向運行時常量池中該棧幀所屬的方法引用,持有這個引用是爲了支持方法調用過程中的動態連接(Dynamic Linking)。我們知道,在 Class 文件的常量池中存在大量的符號引用,字節碼中的方法調用指令就以常量池裏指向的方法的符號引用作爲參數。這些符號引用一部分會在類加載階段或者第一次使用時就被轉化爲直接引用,稱爲靜態解析。另外一部分將在每一次運行期間都轉化爲直接引用,稱爲動態連接。

4. 方法返回地址

當一個方法開始執行後,只有兩種方式退出這個方法,要麼正常結束,要麼發生異常。無論採用何種退出方法的方式,都必須返回最初方法調用的位置,程序才能繼續執行。一般來說,方法正常退出時,主調方法的 PC 計數器的值就可以作爲返回地址,在棧幀中保存。而方法異常退出,返回地址是通過異常處理器表來確定的,棧幀一般不保存這個信息。

5. 附加信息

Java 虛擬機規範允許虛擬機實現增加一些規範裏沒有描述的信息到棧幀之中,例如與調試、性能收集相關的信息,這部分信息完全取決於虛擬機的具體實現。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章