JVM棧

Java中的棧
每當啓用一個線程時,JVM就爲他分配一個Java棧,棧是以幀爲單位保存當前線程的運行狀態。某個線程正在執行的方法稱爲當前方法,當前方法使用的棧幀稱爲當前幀,當前方法所屬的類稱爲當前類,當前類的常量池稱爲當前常量池。當線程執行一個方法時,它會跟蹤當前常量池。
每當線程調用一個Java方法時,JVM就會在該線程對應的棧中壓入一個幀,這個幀自然就成了當前幀。當執行這個方法時,它使用這個幀來存儲參數、局部變量、中間運算結果等等。
Java棧上的所有數據都是私有的。任何線程都不能訪問另一個線程的棧數據。所以我們不用考慮多線程情況下棧數據訪問同步的情況。
像方法區和堆一樣,Java棧和幀在內存中也不必是連續的,幀可以分佈在連續的棧裏,也可以分佈在堆裏
Java棧的組成元素——棧幀
棧幀由三部分組成:局部變量區、操作數棧、幀數據區。局部變量區和操作數棧的大小要視對應的方法而定,他們是按字長計算的。但調用一個方法時,它從類型信息中得到此方法局部變量區和操作數棧大小,並據此分配棧內存,然後壓入Java棧。
棧幀(Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處理動態鏈接(Dynamic Linking)、方法返回值和異常分派(Dispatch Exception)。
棧幀隨着方法調用而創建,隨着方法結束而銷燬——無論方法是正常完成還是異常完成(拋出了在方法內未被捕獲的異常)都算作方法結束。棧幀的存儲空間分配在 Java 虛擬機棧之中,每一個棧幀都有自己的局部變量表(Local Variables)、操作數棧(Operand Stack)和指向當前方法所屬的類的運行時常量池的引用。局部變量表和操作數棧的容量是在編譯期確定,並通過方法的 Code 屬性保存及提供給棧幀使用。因此,棧幀容量的大小僅僅取決於 Java 虛擬機的實現和方法調用時可被分配的內存。
在一條線程之中,只有目前正在執行的那個方法的棧幀是活動的。這個棧幀就被稱爲是當前棧幀(Current Frame),這個棧幀對應的方法就被稱爲是當前方法(Current Method),定義這個方法的類就稱作當前類(Current Class)。對局部變量表和操作數棧的各種操作,通常都指的是對當前棧幀的對局部變量表和操作數棧進行的操作。
如果當前方法調用了其他方法,或者當前方法執行結束,那這個方法的棧幀就不再是當前棧幀了。當一個新的方法被調用,一個新的棧幀也會隨之而創建,並且隨着程序控制權移交到新的方法而成爲新的當前棧幀。當方法返回的之際,當前棧幀會傳回此方法的執行結果給前一個棧幀,在方法返回之後,當前棧幀就隨之被丟棄,前一個棧幀就重新成爲當前棧幀了。
請讀者特別注意,棧幀是線程本地私有的數據,不可能在一個棧幀之中引用另外一條線程的棧幀。
局部變量表
每個棧幀內部都包含一組稱爲局部變量表(Local Variables)的變量列表。棧幀中局部變量表的長度由編譯期決定,並且存儲於類和接口的二進制表示之中,既通過方法的Code 屬性保存及提供給棧幀使用。
一個局部變量可以保存一個類型爲 boolean、byte、char、short、float、reference 和 returnAddress 的數據,兩個局部變量可以保存一個類型爲 long 和 double 的數據。
局部變量使用索引來進行定位訪問,第一個局部變量的索引值爲零,局部變量的索引值是從零至小於局部變量表最大容量的所有整數。

long 和 double 類型的數據佔用兩個連續的局部變量,這兩種類型的數據值採用兩個局部變量之中較小的索引值來定位。例如我們講一個 double 類型的值存儲在索引值爲 n 的局部變量中,實際上的意思是索引值爲 n 和 n+1 的兩個局部變量都用來存儲這個值。索引值爲 n+1 的局部變量是無法直接讀取的,但是可能會被寫入,不過如果進行了這種操作,就將會導致局部變量 n 的內容失效掉。

上文中提及的局部變量 n 的 n 值並不要求一定是偶數,Java 虛擬機也不要求 double 和 long 類型數據採用 64 位對其的方式存放在連續的局部變量中。虛擬機實現者可以自由地選擇適當的方式,通過兩個局部變量來存儲一個 double 或 long 類型的值。

Java 虛擬機使用局部變量表來完成方法調用時的參數傳遞,當一個方法被調用的時候,它的參數將會傳遞至從 0 開始的連續的局部變量表位置上。特別地,當一個實例方法被調用的時候,第 0 個局部變量一定是用來存儲被調用的實例方法所在的對象的引用(即 Java 語言中的“this”關鍵字)。後續的其他參數將會傳遞至從 1 開始的連續的局部變量表位置上。
操作數棧
每一個棧幀(§2.6)內部都包含一個稱爲操作數棧(Operand Stack)的後進先出(Last-In-First-Out,LIFO)棧。棧幀中操作數棧的長度由編譯期決定,並且存儲於類和接口的二進制表示之中,既通過方法的 Code 屬性保存及提供給棧幀使用。
在上下文明確,不會產生誤解的前提下,我們經常把“當前棧幀的操作數棧”直接簡稱爲“操作數棧”。
操作數棧所屬的棧幀在剛剛被創建的時候,操作數棧是空的。Java 虛擬機提供一些字節碼指令來從局部變量表或者對象實例的字段中複製常量或變量值到操作數棧中,也提供了一些指令用於從操作數棧取走數據、操作數據和把操作結果重新入棧。在方法調用的時候,操作數棧也用來準備調用方法的參數以及接收方法返回結果。

舉個例子,iadd 字節碼指令的作用是將兩個 int 類型的數值相加,它要求在執行的之前操作數棧的棧頂已經存在兩個由前面其他指令放入的 int 型數值。在 iadd 指令執行時,2 個 int 值從操作棧中出棧,相加求和,然後將求和結果重新入棧。在操作數棧中,一項運算常由多個子運算(Subcomputations)嵌套進行,一個子運算過程的結果可以被其他外圍運算所使用。

每一個操作數棧的成員(Entry)可以保存一個 Java 虛擬機中定義的任意數據類型的值,包括 long 和 double 類型。在操作數棧中的數據必須被正確地操作,這裏正確操作是指對操作數棧的操作必須與操作數棧棧頂的數據類型相匹配,例如不可以入棧兩個 int 類型的數據,然後當作 long 類型去操作他們,或者入棧兩個 float 類型的數據,然後使用 iadd 指令去對它們進行求和。有一小部分 Java 虛擬機指令(例如 dup 和 swap 指令)可以不關注操作數的具體數據類型,把所有在運行時數據區中的數據當作裸類型(Raw Type)數據來操作,這些指令不可以用來修改數據,也不可以拆散那些原本不可拆分的數據,這些操作的正確性將會通過 Class 文件的校驗過程(§4.10)來強制保障。

在任意時刻,操作數棧都會有一個確定的棧深度,一個 long 或者 double 類型的數據會佔用兩個單位的棧深度,其他數據類型則會佔用一個單位深度。
動態鏈接
每一個棧幀內部都包含一個指向運行時常量池(§2.5.5)的引用來支持當前方法的代碼實現動態鏈接(Dynamic Linking)。在 Class 文件裏面,描述一個方法調用了其他方法,或者訪問其成員變量是通過符號引用(Symbolic Reference)來表示的,動態鏈接的作用就是將這些符號引用所表示的方法轉換爲實際方法的直接引用。類加載的過程中將要解析掉尚未被解析的符號引用,並且將變量訪問轉化爲訪問這些變量的存儲結構所在的運行時內存位置的正確偏移量。

由於動態鏈接的存在,通過晚期綁定(Late Binding)使用的其他類的方法和變量在發生變化時,將不會對調用它們的方法構成影響。
方法正常調用完成
方法正常調用完成是指在方法的執行過程中,沒有任何異常被拋出——包括直接從 Java 虛擬機之中拋出的異常以及在執行時通過 throw 語句顯式拋出的異常。如果當前方法調用正常完成的話,它很可能會返回一個值給調用它的方法,方法正常完成發生在一個方法執行過程中遇到了方法返回的字節碼指令的時候,使用哪種返回指令取決於方法返回值的數據類型(如果有返回值的話)。

在這種場景下,當前棧幀承擔着回覆調用者狀態的責任,其狀態包括調用者的局部變量表、操作數棧和被正確增加過來表示執行了該方法調用指令的程序計數器等。使得調用者的代碼能在被調用的方法返回並且返回值被推入調用者棧幀的操作數棧後繼續正常地執行。
方法異常調用完成

方法異常調用完成是指在方法的執行過程中,某些指令導致了 Java 虛擬機拋出異常,並且虛擬機拋出的異常在該方法中沒有辦法處理,或者在執行過程中遇到了 athrow 字節碼指令顯式地拋出異常,並且在該方法內部沒有把異常捕獲住。如果方法異常調用完成,那一定不會有方法返回值返回給它的調用者。


本文轉載自:http://blog.csdn.net/kobejayandy/article/details/41544483

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