文章目錄
- 簡介
- 1. 運行時堆疊幀結構
- 1.1 區域變數表
- 1.2 運算元堆疊
- 1.3 動態連接
- 1.4 方法回傳地址
- 參考
簡介
JVM以方法作為最基本的執行單元,
堆疊幀則是用于支持JVM進行方法呼叫與方法執行背后的資料結構,同樣它也是JVM運行時資料區中的虛擬機堆疊的堆疊元素,
1. 運行時堆疊幀結構
每個方法從呼叫開始到執行結束,都對應著一個堆疊幀在虛擬機堆疊中從入堆疊到出堆疊的程序,
堆疊幀存放了哪些資訊?這些資訊都存放在哪?
每個堆疊幀都包括了區域變數表、運算元堆疊、動態連接、方法回傳地址和一些額外附加資訊,在編譯Java程式原始碼時,堆疊幀需要多大的區域變數表、多深的運算元堆疊就已經被分析計算出來并寫入到方法表的Code屬性中,
方法呼叫的時候哪個堆疊幀才是“當前堆疊幀”?
假如一個執行緒的方法呼叫鏈很長,同一時刻同一執行緒中,在呼叫堆疊的所有方法同時處于執行狀態,在執行引擎的角度來說,在活動執行緒中,只有位于堆疊頂的方法才是運行的,只有位于堆疊頂的堆疊幀才是生效的,被稱為當前堆疊幀,與之關聯的方法稱為當前方法,執行引擎所運行的所有位元組碼指令都只針對當前堆疊幀進行操作,
下圖是虛擬機堆疊與堆疊幀的總體結構,接下來我們來一起學習堆疊幀中的區域變數表、運算元堆疊、動態連接、方法回傳地址等各部分的作用和資料結構,

1.1 區域變數表
區域變數表是一組變數值的存盤空間,用于存放方法引數和方法內部定義的區域變數,
方法體里面的代碼經過javac編譯器處理之后,最終變為位元組碼指令存盤在Code屬性內,Code屬性出現在方法表的屬性集合中(介面或抽象類中的方法不存在Code屬性),在Code屬性max_locals資料項中確定了該方法所需分配的區域變數表的最大容量,
如下列舉了方法表結構及屬性表中Code屬性的結構:
| 方法表結構 | ||
|---|---|---|
| 型別 | 名稱 | 數量 |
| u2 | access_flags(訪問標志位) | 1 |
| u2 | name_index(欄位簡單名稱) | 1 |
| u2 | descriptor_index(描述符) | 1 |
| u2 | attributes_count(屬性表存在個數) | 1 |
| attribute_info(屬性表) | attributes | attributes_count |
| 屬性表結構 | ||
|---|---|---|
| 型別 | 名稱 | 數量 |
| u2 | attribute_name_index | 1 |
| u4 | attribute_length | 1 |
| u1 | info | attribute_length |
| Code屬性表結構 | ||
|---|---|---|
| 型別 | 名稱 | 數量 |
| u2 | attribute_name_index 固定“Code” | 1 |
| u4 | attribute_length 屬性表長度 | 1 |
| u2 | max_stack 運算元堆疊最大深度 | 1 |
| u2 | max_locals 區域變數表所需存盤空間 | 1 |
| u4 | code_length 位元組碼長度 | 1 |
| u1 | code 存盤位元組碼指令的位元組流 | code_length |
| u2 | exception_table_length | 1 |
| exception_info | exception_table | exception_table_length |
| u2 | attributes_count | 1 |
| attribute_info | attributes | attributes_count |
區域變數表的容量max_locals以變數槽(Slot)為最小單位,對于對于byte、char、float、int、short、boolean和returnAddress等長度不超過32位的資料型別,每個區域變數占用一個變數槽,而double和long這兩種64位的資料型別則需要兩個變數槽來存放,最后一種為reference型別,
區域變數表是執行緒安全?
區域變數表是建立在執行緒堆疊中的,屬于執行緒私有的資料,
reference資料型別用于存盤什么?有什么作用?
reference表示對一個物件實體的參考,《Java虛擬機規范》沒有規定其長度也為明確應該是什么資料結構,虛擬機主要通過reference做兩件事:
- 根據參考直接或間接地查找物件在Java堆中資料存放的起始地址或索引,
- 根據參考直接或間接地查找到物件所屬資料型別在方法區中的存盤的型別資訊,
區域變數表具體存放哪些資訊呢?
方法引數(包括實體方法中的隱式引數this)、顯示例外處理引數(try-catch()中定義的例外)、方法體中定義的區域變數,
方法呼叫時區域變數表的作業程序:
方法被呼叫時,JVM會使用區域變數表來完成引數值到引數變數串列的傳遞程序,即實參到形參的傳遞:如果執行的是實體方法(沒有static修飾的方法),區域變數表中第0位索引的變數槽默認是用于傳遞方法所屬物件實體的參考,其余引數按照引數表順序排列,占用從1開始的區域變數槽,引數表分配完畢之后,再根據方法內部定義的變數順序和作用域分配其余的變數槽,
max_locals的值是方法中的區域變數所占變數槽數之和嗎?
答案是否定的,運算元堆疊和區域變數表直接決定該方法的堆疊幀耗費的記憶體,不必要的運算元堆疊深度和變數槽數量會造成記憶體浪費,JVM的做法是將區域變數表中的變數槽進行重用,當代碼執行超出一個區域變數的作用域時,這個區域變數所占的變數槽可以被其他區域變數所使用,Javac編譯器會根據變數的作用域來分配變數槽給各個變數使用,根據同時生存的最大區域變數數量和型別計算出max_locals的大小,
1.2 運算元堆疊
也稱為操作堆疊,它是一個后入先出(LIFO)堆疊,
運算元堆疊的最大深度在編譯時寫入到Code屬性的max_stacks資料項之中,運算元堆疊的每一個元素都可以是包括long和double在內的任意Java資料型別,Javac編譯器的資料流分析作業保證了在方法執行時,運算元堆疊的深度都不會超過在max_stacks資料項中設定的最大值,
方法呼叫時運算元堆疊的作業程序:
方法開始執行時,該方法的運算元堆疊是空的,執行程序中,位元組碼指令向運算元堆疊中寫入和提取內容,即出堆疊和入堆疊操作, 例如位元組碼指令iadd,在運行時要求運算元堆疊中最接近堆疊頂的兩個元素已經存入了兩個int型的數值,當執行這個指令時,兩個int值出堆疊并相加,然后將相加的結果重新入堆疊,
1.3 動態連接
Class檔案的常量池中存有大量的符號參考,位元組碼中的方法呼叫指令就以常量池里指向方法的符號參考作為引數,這些符號參考一部分會在類加載階段或者第一次使用的時候就被轉化為直接參考,這種轉化被稱為靜態決議,另外一部分將在每一次運行期間都轉化為直接參考,這部分就稱為動態連接,
每個堆疊幀都包含一個指向運行時常量池中該堆疊幀所屬方法的參考,持有這個參考是為了支持方法呼叫程序中的動態連接,
1.4 方法回傳地址
當方法開始執行后,只有兩種方式退出這個方法:
- 當執行引擎遇到任意方法回傳的位元組碼指令,稱為“正常呼叫完成” ,
- 在方法執行的程序中遇到例外且例外沒有在方法體內得到妥善處理,稱為“例外呼叫完成 ”,
方法退出后,都必須回傳到方法被呼叫時的位置,方法回傳時可能需要在堆疊幀中保存一些資訊,用來幫助恢復它的上層主調方法的執行狀態,一般情況下,方法正常調動完成,呼叫者的PC計數器的值就可以作為回傳地址,堆疊幀中很可能會保存這個計數器值, 方法例外呼叫完成, 則反之,
參考
《深入理Java虛擬機》周志明
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/254881.html
標籤:其他
上一篇:一周一個小朋友系列——YOLOV1 paper Analysis
下一篇:記一次簡單安全事件分析之溯源
