JVM中棧的frames詳解


簡介

我們知道JVM運行時數據區域專門有一個叫做Stack Area的區域,專門用來負責線程的執行調用。那么JVM中的棧到底是怎么工作的呢?快來一起看看吧。

JVM中的棧

小師妹:F師兄,JVM為每個線程的運行都分配了一個棧,這個棧到底是怎么工作的呢?

小師妹,我們先看下JVM的整體運行架構圖:

我們可以看到運行時數據區域分為5大部分。

堆區是存儲共享對象的地方,而棧區是存儲線程私有對象的地方。

因為是棧的結構,所以這個區域總是LIFO(Last in first out)。我們考慮一個方法的執行,當方法執行的時候,就會在Stack Area中創建一個block,這個block中持有對本地對象和其他對象的引用。一旦方法執行完畢,則這個block就會出棧,供其他方法訪問。

Frame

JVM中的stack area是由一個個的Frame組成的。

Frame主要用來存儲數據和部分結果,以及執行動態鏈接,方法的返回值和調度異常。

每次調用方法時都會創建一個新Frame。當Frame的方法調用完成時,無論該方法是正常結束還是異常結束(它引發未捕獲的異常),這個frame都會被銷毀。

Frame是從JVM中的stack area中分配的。

每個frame都由三部分組成,分別是自己的local variables數組,自己的operand stack,以及對當前方法的run-time constant pool的引用。

在線程的執行過程中,任何一個時刻都只有一個frame處於活動狀態。這個frame被稱為current frame,它的方法被稱為current 方法,定義當前方法的類是當前類。

如果frame中的方法調用另一個方法或該frame的方法結束,那么這個frame將不再是current frame。

每次調用新的方法,都會創建一個新的frame,並將控制權轉移到調用新的方法生成的框架。

在方法返回時,當前frame將其方法調用的結果(如果有的話)傳回上一個frame,並結束當前frame。

請注意,由線程創建的frame只能有該線程訪問,並且不能被任何其他線程引用。

Local Variables本地變量

每個frame都包含一個稱為其本地局部變量的變量數組。frame的局部變量數組的長度是在編譯的時候確定的。

單個局部變量可以保存以下類型的值:boolean, byte, char, short, int, float, reference, 或者 returnAddress。

如果對於long或double類型的值需要使用一對局部變量來存儲。

局部變量因為存儲在數組中,所以直接通過數字的索引來定位和訪問。

注意,這個數組的索引值是從0開始,到數組長度-1結束。

單個局部變量直接通過索引來訪問就夠了,那么對於占用兩個連續局部變量的long或者double類型來說,怎么訪問呢?

比如說一個long類型占用數組中的n和n+1兩個變量,那么我們可以通過索引n值來訪問這個long類型,而不是通過n+1來訪問。

注意,在JVM中,並不一定要求這個n是偶數。

那么這些局部變量有什么用呢?

Java虛擬機使用局部變量在方法調用時傳遞參數。

我們知道在java中有兩種方法,一種是類方法,一種是實例方法。

在類方法調用中,所有參數都從局部變量0開始在連續的局部變量中傳遞。

在實例方法調用中,局部變量0始終指向的是該實例對象,也就是this。也就是說真實的參數是從局部變量1開始存儲的。

Operand Stacks

在每個frame內部,又包含了一個LIFO的棧,這個棧叫做Operand Stack。

剛開始創建的時候,這個Operand Stack是空的。然后JVM將local variables中的常量或者值加載到Operand Stack中去。

然后Java虛擬機指令從操作數堆棧中獲取操作數,對其進行操作,然后將結果壓回操作數堆棧。

比如說,現在的Operand Stack中已經有兩個值,1和2。

這個時候JVM要執行一個iadd指令,將1和2相加。那么就會先將stack中的1和2兩個數取出,相加后,將結果3再壓入stack。

最終stack中保存的是iadd的結果3。

注意,在Local Variables本地變量中我們提到,如果是long或者double類型的話,需要兩個本地變量來存儲。而在Operand Stack中,一個值可以表示任何Java虛擬機類型的值。也就是說long和double在Operand Stack中,使用一個值就可以表示了。

Operand Stack中的任何操作都必須要確保其類型匹配。像之前提到的iadd指令是對兩個int進行相加,如果這個時候你的Operand Stacks中存儲的是long值,那么iadd指令是會失敗的。

在任何時間點,操作數堆棧都具有關聯的深度,其中long或double類型的值對該深度貢獻兩個單位,而任何其他類型的值則貢獻一個單位深度。

Dynamic Linking動態鏈接

什么是動態鏈接呢?

我們知道在class文件中除了包含類的版本、字段、方法、接口
等描述信息外,還有一項信息就是常量池(constant pool table),用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)。

所謂字面量就是常說的常量,可以有三種方式,分別是:文本字符串,八種基本類型和final類型的常量。

而符號引用是指用符號來描述所引用的目標。

符號引用和直接引用有什么區別呢? 我們舉個例子。

比如我們定義了String name="jack", 其中jack是一個字面量,會在字符串常量池(String Pool)中保存一份。

如果我們存儲的時候,存的是name,那么這個就是符號引用。

如果我們存儲的是jack在字符串常量池中地址,那么這個就是直接引用。

從上面的介紹我們可以知道,為了實現最終的程序正常運行,所有的符號引用都需要轉換成為直接引用才能正常執行。

而這個轉換的過程,就叫做動態鏈接。

動態鏈接將這些符號方法引用轉換為具體的方法引用,根據需要加載類以解析尚未定義的符號,並將變量訪問轉換為與這些變量的運行時位置關聯的存儲結構中的適當偏移量。

方法執行完畢

方法執行完畢有兩種形式,一種是正常執行完畢,一種是執行過程中拋出了異常。

正常執行完畢的方法可以值返回給調用方。

這種情況下frame的作用就是恢復調用程序的狀態,包括其局部變量和操作數堆棧,並適當增加調用程序的程序計數器以跳過方法調用指令。

如果方法中拋出了異常,那么該方法將不會有值返回給調用方。

本文已收錄於:http://www.flydean.com/jvm-thread-stack-frames/

最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM