1. 程式人生 > >Java編譯原理--執行時棧幀結構

Java編譯原理--執行時棧幀結構

Java語言在剛剛誕生的時候提出過一句著名的口號“一次編寫,到處執行”,這句話充分的表達了開發人員對於衝破平臺界限的渴望,也解釋了Java語言跟平臺無關的設定。

 一、 概述

Java虛擬機器規定了虛擬機器執行位元組碼的概念模型,這個模型是各類虛擬機器的外觀結構,不同的虛擬機器可以有不同的實現,但是從外部看起來它們都是統一的,輸入的是二進位制位元組流,經過執行引擎處理之後,輸出執行結果。當二進位制位元組流進入記憶體後,在執行過程中,類、類變數、欄位等都已經在記憶體中初始化,那麼在方法執行過程中,方法體各結構如何儲存,方法呼叫時如何實現的呢?本文將討論這些內容。

 1、 執行時的資料區

 Java虛擬機器在執行過程中會把它所管理的記憶體區域分為不同的區域,,這些區域都有各自的用途、建立及銷燬時間,有的區域隨著虛擬機器的啟動而存在,有的區域則依賴使用者執行緒的呼叫而建立和銷燬,根據Java虛擬機器規範,Java虛擬機器執行時應該包括方法區、堆、虛擬機器棧、本地方法棧和程式計數器區域,各區域如下圖所示:

程式計數器是一塊很小的區域,它是用來記錄當前執行緒所執行的位元組碼的行號指示器,位元組碼執行時,虛擬機器是通過改變程式計數器的值來尋找位元組碼指令的,程式計數器是執行緒的私有區域,每個執行緒都會有自己的程式計數器。

虛擬機器棧跟程式計數器類似,也是執行緒私有的區域,它的生命週期跟執行緒一致。虛擬機器棧描述的是方法執行時的記憶體模型每個方法執行時都會在虛擬機器棧中生成一塊空間稱為棧幀,用於儲存方法所需要的各種變數和資料,方法的呼叫過程就伴隨著一個棧幀在本地方法棧中的入棧和出棧過程。

本地方法棧跟虛擬機器棧作用相似,也是執行緒私有區域,用於方法呼叫期間生成一塊地址以儲存變數,所不同的是,虛擬機器棧為Java方法(二進位制位元組碼)服務,本地方法棧是為虛擬機器用到的本地方法(native)服務。

堆是記憶體中最大的一塊區域,並且堆是Java虛擬機器中所有執行緒共享的,隨著虛擬機器的啟動而建立,隨著虛擬機器的退出而銷燬,大部分的物件例項都要在堆上分配,虛擬機器規範規定,Java堆可以不是連續的物理空間,只需要在邏輯上連續即可。

方法區跟Java堆一樣,也是執行緒共享的區域,它用於儲存虛擬機器載入的類、常量、靜態變數、即時編譯器編譯後的程式碼等資料,常量池即在方法區存在。

 2、 棧幀

 棧幀是用於虛擬機器方法呼叫和方法執行的資料區域,它是虛擬機器棧的棧元素,棧幀中儲存了方法的區域性變量表、運算元棧、動態連結和方法返回地址等資訊。一個執行緒呼叫的方法可能很多,但是每個執行緒在一個時間點,只能執行一個方法,也就是處於棧幀頂部的棧幀才是有效棧幀,稱為當前棧幀,執行的方法稱為當前方法。區域性變量表的大小和運算元棧的深度在程式碼編譯階段已經確定,並且將數值寫入了Code屬性中,因此一個棧幀分配多少記憶體,不會受到執行期變數資料的影響,僅僅取決於具體的虛擬機器實現。一個典型的棧幀結構如下圖所示:

 

 3、 區域性變量表

區域性變量表是一組變數值儲存空間,用於儲存方法引數和方法內定義的區域性變數,在Java程式編譯為class檔案時,區域性變量表的最大容量儲存在Code屬性的max_locals資料項中。

區域性變量表中最小的單位是slot(變數槽),虛擬機器沒有明確規定一個slot的大小,只是說了一個slot應該能存放一個boolean、byte、char、short、int、float、reference、returnAddress型別,這些型別都可以使用32位或者更小的空間儲存,並且可以用slot儲存long、double等64位的資料型別,只要在邏輯上看起來跟32位虛擬機器一致就行。

Java虛擬機器通過索引的方式訪問區域性變量表,索引位置從0開始至索引的最大位置,也即是slot的數量為-1。雖然區域性變量表用於儲存方法引數和方法內部的區域性變數,但是方法引數和方法內部的區域性變數加起來的數值並不一定等於區域性變量表的大小,因為區域性變量表的空間是可以複用的。區域性變量表示意圖如下圖所示:

 4、 運算元棧

 運算元棧也稱為操作棧,是一個後入先出的資料結構,同區域性變量表一樣,運算元棧的深度也是在編譯階段確定,並且儲存在Code屬性的max_stacks資料項中,運算元棧中的資料可以是任意型別的,32位型別的資料佔據一個棧容量,64位型別的資料佔據兩個棧容量。
    運算元棧的使用過程包括以下幾項:
    1)棧幀剛建立時,運算元棧是空的;
    2)虛擬機器可以對運算元棧進行入棧操作,比如把區域性變量表或方法引數中的資料入棧;
    3)虛擬機器也可以對運算元棧進行出棧操作;
    4)向其他方法傳遞的引數,也存在運算元棧中;
    5)呼叫其他方法的返回結果,也儲存在運算元棧中。

概念模型中,棧幀之間是相互隔離的,但是在具體的虛擬機器實現中,棧幀之間都會進行部分重疊,這樣在方法呼叫時,可以減少部分引數的複製和傳遞,加快執行過程。運算元棧示意圖如下圖所示:

 

5、動態連結 

 一個方法呼叫另外的方法,或者一個類使用另外一個類的成員變數時,都需要通過直接引用來呼叫。程式碼經過編譯後,會形成位元組碼檔案,位元組碼檔案中儲存的都是符號引用,執行引擎需要使用直接引用,符號引用需要轉換為直接引用,一部分符號引用會在類載入或者第一次使用時進行解析,稱為靜態解析;另外一部分符號引用需要在程式碼執行時動態進行解析,稱為動態連結。

 6、方法返回地址

 當一個方法執行結束後,有兩種方式可以退出這個方法,第一種是遇到方法的返回指令,這時候會有返回值傳遞給方法的呼叫者,這種返回方式稱為正常返回;第二種返回方式是異常返回,方法執行過程中遇到內部異常,或者在異常表中沒有搜尋到匹配的異常處理器,會導致方法退出,這種方式不會給方法呼叫者返回值。

無論何種方法退出,都需要返回到方法呼叫者的位置,程式才能繼續執行,正常退出時,棧幀中會存放方法呼叫者的PC計數器的值,用以回覆呼叫者的執行狀態;異常退出時,需要通過異常處理表來確定,這時候棧幀中不存放異常資訊。