1. 程式人生 > 實用技巧 >JVM中棧的frames詳解

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/

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

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