Java虛擬機知識整理——運行時棧幀結構

運行時棧幀

棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區中的虛擬機棧的棧元素。棧幀存儲了方法的局部表量表、操作數棧,動態連接和方法返回地址等信息。每一個方法從調用開始執行完成的過程,都對應着一個棧幀在虛擬機棧裏面從入棧到出棧的過程。
每一個棧幀都包括了局部變量表、操作數棧、動態連接、方法返回地址和一些額外的附加信息。一個線程中的方法調用鏈可能會很長,很多方法都同時處於執行狀態。對於執行引擎來說,在活動縣城中,只有位於棧頂的棧幀纔是有效的,稱爲當前棧幀,與這個棧幀相關的方法稱爲當前方法。執行引擎運行的所有字節碼指令都值針對當前棧幀進行操作。

局部變量表

局部變量表是一組變量值存儲空間,用於存放方法參數和方法內不定義的局部變量。在Java程序編譯爲Class文件時,就在方法的Code屬性max_locals數據項中確定了該方法所需要的分配局部變量表的最大容量。
局部表量表的容量以變量槽爲最小單位,虛擬機規範中並沒有明確指明一個Slot應占用的內存空間大小,只是很有導向性地說道每個Slot都應該能存放一個boolean、byte、char、short、int、float、reference或returnAddress類型的數據,這8種數據類型,都可以使用32位或更小的物理內存來存放,但這種描述與明確指出“每個Slot佔用32位長度的內存空間”是有一些差別的,它允許Slot的長度可以隨着處理器、操作系統或虛擬機的不同而發生變化。
虛擬機的八種數據類型,前六種是Java的數據類型,第七種reference類型表示對一個對象實例的引用,虛擬機規範既沒有說明它的長度,也沒有明確指出這種引用應有怎樣的結構。但一般來說,虛擬機實現至少都應當知道這個引用做到兩點,意識從此引用中直接或間接地查找到對象在Java堆中的數據存放的起始地址索引,二是此引用中直接或間接地查找到對象所屬數據類型在方法區中的存儲的類型信息,否則無法實現Java語言規範中定義的語法結構約束。第八種returnAddress類型目前已經很少見了,它是爲字節碼指令jsr、jsr_w和ret服務的,指向了一條字節碼指令的地址,很古老的Java虛擬機曾經使用這幾條指令來實現異常處理,現在已經由已經由異常表代替。
對於64位的數據類型,虛擬機會以高位對齊的方式爲其分配兩個連續的Slot空間。
虛擬機通過索引定位的方式使用局部變量表,索引值的範圍是從0開始至局部表量表最大的Slot數量。如果訪問的是32爲數據類型的變量,索引n就代表了使用第n個Slot,如果是64位數據類型的變量,則說明會同時使用n和n+1兩個Slot。對於兩個相鄰的共同存放一個64位數據的兩個Slot,不允許採用任何方式單獨訪問其中的某一個,Java虛擬機規範中明確要求瞭如果遇到進行這種操作的字節碼序列,虛擬機應該在類加載的校驗階段拋出異常。
在方法執行時,虛擬機是使用局部變量表完成參數值到參數變量表到參數變量列表的傳遞過程的,如果執行的是實例方法,那局部表量表中第0爲所以的Slot默認是用於傳遞方法所屬對象實例的引用,在方法中可以通過關鍵字“this”來訪問這個隱含的參數。崎嶇參數則按照參數表排列,佔用從1開始的局部標量slot,參數表分配完畢後,再根據方法體內部定義的變量順序和作用域分配其餘的Slot。
爲了儘可能節省幀棧空間,局部變量表中的Slot是可以寵用的,方法體中定義的變量,器作用域並不一定會覆蓋整個方法體,如果當前字節碼PC計數器的值已經超過了某個變量的作用域,那這個變量對應的Slot就可以交給其他變量使用。不過這樣的設計除了節省棧幀的空間之外,還會伴隨一些額外的副作用,例如,在某些情況下,Slot的複用會直接影響到系統的垃圾收集行爲。

操作數棧

操作數棧,也常成爲操作棧,它是一個後入現出棧。同局部變量一樣,操作數棧的最大深度也在編譯的時候寫到了Code屬性的max_stacks數據項中。操作書棧的每一個元素可以是任意的Java數據類型,包括long和double。32位數據類型所佔的棧容量爲1,64位數據類型所佔用的棧容量爲2。在方法執行的任何時候,操作數棧的深度都不會超過在max_stacks數據項中設定的最大值。
當一個方法剛剛開始執行的時候,這個方法低的操作數棧是空的,在方法的執行過程中,會有各種字節碼指令往操作數棧中寫入和提取內容,也就是入棧/出棧操作。
操作數棧中元素的數據類型不許與字節碼指令的序列嚴格匹配,在貶義程序代碼的時候,編譯器要嚴格保證這一點,在類校驗階段的數據流分析中還要再驗證這一點。

動態連接

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

方法返回地址

每當一個方法開始執行後,只有兩種方式可以退出這個方法。
第一種是執行引擎遇到的任意一個返回的字節碼指令,這個時候可能會有返回值傳遞給上層的方法調用者,是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法的方式稱爲正常完成出口。
另外一種退出方式是,在方法執行過程中遇到了異常,並且這個異常沒有在方法體內得到處理,無聊時Java虛擬機內部產生的異常,還是代碼中使用athrow字節碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出,這種退出方法的方式稱爲異常完成出口。一個方法使用異常完成出口的方式退出,是不會給它的上層調用者產生任何返回值的。
無論採用何種退出方式,在方法退出之後,就需要放回到方法調用的位置,程序才能繼續執行,方法返回時可能需要在棧幀中保存就一些信息,用來幫助恢復它的上層方法的執行狀態。一般來說,方法正常退出是,調用者的PC計數器的值可以作爲返回地址,棧幀中和可能會保存這個計數器值,而方法異常退出時,返回地址是要通過異常持利器表來確定的,棧幀中一般不會保存着部分信息。
方法的退出過程實際上就等同於吧當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,把返回值壓入調用者棧幀的操作數棧中,調整PC計數器的值以執行方法調用指令後面的一條指令等。

附加信息

虛擬機規範允許具體的虛擬機實現增加一些規範裏沒有描述的信息到棧幀中,例如與調試想斷的信息,這部分信息將完全取決於具體的虛擬機實現,這裏不再詳述。在實際開發中,一般會把動態連接、方法返回地址和其他附加信息全部歸爲一類,稱爲棧幀信息。

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