JVM运行时栈帧结构
棧幀(Stack Frame)是用于支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區的虛擬機棧(Virtual Machine Stack)的棧元素。棧幀存儲了方法的局部變量表,操作數棧,動態連接和方法返回地址等信息。第一個方法從調用開始到執行完成,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
每一個棧幀都包括了局部變量表,操作數棧,動態連接,方法返回地址和一些額外的附加信息。在編譯代碼的時候,棧幀中需要多大的局部變量表,多深的操作數棧都已經完全確定了,并且寫入到了方法表的Code屬性中,因此一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決于具體虛擬機的實現。
一個線程中的方法調用鏈可能會很長,很多方法都同時處理執行狀態。對于執行引擎來講,活動線程中,只有虛擬機棧頂的棧幀才是有效的,稱為當前棧幀(Current Stack Frame),這個棧幀所關聯的方法稱為當前方法(Current Method)。執行引用所運行的所有字節碼指令都只針對當前棧幀進行操作。棧幀的概念結構如下圖所示:
1. 局部變量表
局部變量表(Local Variable Table)是一組變量值存儲空間,用于存放方法參數和方法內部定義的局部變量。在編譯Class文件時,就在方法的Code屬性的max_locals數據項中已經確定了該方法需要分配的局部變量表的最大容量。
變量槽 (Variable Slot)是局部變量表的最小單位,沒有強制規定大小為 32 位,雖然32位足夠存放大部分類型的數據。一個 Slot可以存放 boolean、byte、char、short、int、float、reference和 returnAddress 8種類型。其中 reference 表示對一個對象實例的引用,通過它可以得到對象在Java 堆中存放的起始地址的索引和該數據所屬數據類型在方法區的類型信息。returnAddress則指向了一條字節碼指令的地址。 對于64位的 long 和 double 變量而言,虛擬機會為其分配兩個連續的 Slot 空間。
虛擬機通過索引定位的方式使用局部變量表。之前我們知道,局部變量表存放的是方法參數和局部變量。當調用方法是非static 方法時,局部變量表中第0位索引的 Slot 默認是用于傳遞方法所屬對象實例的引用,即 “this” 關鍵字指向的對象。分配完方法參數后,便會依次分配方法內部定義的局部變量。
Slot復用驗證
為了節省棧幀空間,局部變量表中的 Slot 是可以重用的。當離開了某些變量的作用域之后,這些變量對應的 Slot 就可以交給其他變量使用。這種機制有時候會影響垃圾回收行為。
public class Main {public static void main(String[] args) {byte[] placeholder = new byte[64*1024*1024];System.gc();} } [GC (System.gc()) 69468K->66384K(188416K), 0.0016481 secs] [Full GC (System.gc()) 66384K->66280K(188416K), 0.0079337 secs] public class Main {public static void main(String[] args) {{byte[] placeholder = new byte[64*1024*1024];}int a = 0;System.gc();} } [GC (System.gc()) 69468K->66368K(188416K), 0.0012876 secs] [Full GC (System.gc()) 66368K->744K(188416K), 0.0055897 secs]可以看到,當我吧byte的聲明單獨放到代碼塊中,然后再執行作用域之外的代碼的時候,gc對slot進行了回收。
注意:jvm不會給局部變量賦初始值,只給全局變量賦初始值。
2. 操作數棧
操作數棧(Operand Stack)也常稱為操作棧,是一個后入先出棧。在Class 文件的Code 屬性的 max_stacks 指定了執行過程中最大的棧深度。Java 虛擬機的解釋執行引擎稱為”基于棧的執行引擎“,這里的棧就是指操作數棧。
方法執行中進行算術運算或者是調用其他的方法進行參數傳遞的時候是通過操作數棧進行的。
jvm對操作數棧的優化
在概念模型中,兩個棧幀是相互獨立的。但是大多數虛擬機的實現都會進行優化,令兩個棧幀出現一部分重疊。令下面的部分操作數棧與上面的局部變量表重疊在一塊,這樣在方法調用的時候可以共用一部分數據,無需進行額外的參數復制傳遞。
3. 動態鏈接
每個棧幀都包含一個執行運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接(Dynamic Linking)。
Class 文件中存放了大量的符號引用,字節碼中的方法調用指令就是以常量池中指向方法的符號引用作為參數。這些符號引用一部分會在類加載階段或第一次使用時轉化為直接引用,這種轉化稱為靜態解析。另一部分將在每一次運行期間轉化為直接引用,這部分稱為動態連接。
4. 方法返回地址
當一個方法開始執行以后,只有兩種方法可以退出當前方法:
當方法返回時,可能進行3個操作:
5. 附加信息
虛擬機規范允許具體的虛擬機實現增加一些規范里沒有描述的信息到棧幀之中,例如與調試相關的信息,這部分信息完全取決于具體的虛擬機實現。在實際開發中,一般會把動態連接、方法返回地址與其他附加信息全部歸為一類,稱為棧幀信息。
總結
以上是生活随笔為你收集整理的JVM运行时栈帧结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces Round #50
- 下一篇: [20180817]校内模拟赛