文章轉載自:http://www.tuicool.com/articles/URZrMnb
jvm為每個新創建的線程都分配一個堆棧。堆棧以幀為單位保存線程的狀態。jvm對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
棧幀(Stack Frame)是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區的虛擬機棧(Virtual Machine Stack)的棧元素。棧幀存儲了方法的局部變量表,操作數棧,動態連接和方法返回地址等信息。第一個方法從調用開始到執行完成,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
每一個棧幀都包括了局部變量表,操作數棧,動態連接,方法返回地址和一些額外的附加信息。在編譯代碼的時候,棧幀中需要多大的局部變量表,多深的操作數棧 都已經完全確定了,並且寫入到了方法表的Code屬性中,因此一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體虛擬機的實 現
一個線程中的方法調用鏈可能會很長,很多方法都同時處理執行狀態。對於執行引擎來講,活動線程中,只有虛擬機棧頂的棧幀才是有效的,稱為當前棧幀 (Current Stack Frame),這個棧幀所關聯的方法稱為當前方法(Current Method)。執行引用所運行的所有字節碼指令都只針對當前棧幀進行操作。棧幀的概念結構如下圖所示:

1.局部變量表
局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。在Java程序編譯為Class文件時,就在方法表的Code屬性的max_locals數據項中確定了該方法需要分配的最大局部變量表的容量。
在方法執行時,虛擬機是使用局部變量表完成參數變量列表的傳遞過程,如果是實例方法,那么局部變量表中的0位索引的Slot默認是用於傳遞方法所屬對象實 例的引用,在方法中可以通過關鍵字“this”來訪問這個隱含的參數,其余參數則按照參數列表的順序來排列,占用從1開始的局部變量Slot,參數表分配 完畢后,再根據方法體內部定義的變量順序和作用域來分配其余的Slot。局部變量表中的Slot是可重用的,方法體中定義的變量,其作用域並不一定會覆蓋整個方法,如果當前字節碼PC計算器的值已經超出了某個變量的作用域,那么這個變量對應的Slot就可以交給其它變量使用。
局部變量不像前面介紹的類變量那樣存在“准備階段”。類變量有兩次賦初始值的過程,一次在准備階段,賦予系統初始值;另外一次在初始化階段,賦予程序員定義的值。因此 即使在初始化階段程序員沒有為類變量賦值也沒有關系,類變量仍然具有一個確定的初始值。但局部變量就不一樣了,如果一個局部變量定義了但沒有賦初始值是不能使用的。
2.操作數棧
操作數棧也常被稱為操作棧,它是一個后入先出棧。同局部變量表一樣,操作數棧的最大深度也是編譯的時候被寫入到方法表的Code屬性的 max_stacks數據項中。操作數棧的每一個元素可以是任意Java數據類型,包括long和double。32位數據類型所占的棧容量為1,64位 數據類型所占的棧容量為2。棧容量的單位為“字寬”,對於32位虛擬機來說,一個”字寬“占4個字節,對於64位虛擬機來說,一個”字寬“占8個字節。
當一個方法剛剛執行的時候,這個方法的操作數棧是空的,在方法執行的過程中,會有各種字節碼指向操作數棧中寫入和提取值,也就是入棧與出棧操作。例如,在做算術運算的時候就是通過操作數棧來進行的,又或者調用其它方法的時候是通過操作數棧來行參數傳遞的。
另外,在概念模型中,兩個棧幀作為虛擬機棧的元素,相互之間是完全獨立的,但是大多數虛擬機的實現里都會作一些優化處理,令兩個棧幀出現一部分重疊。讓下 棧幀的部分操作數棧與上面棧幀的部分局部變量表重疊在一起,這樣在進行方法調用返回時就可以共用一部分數據,而無須進行額外的參數復制傳遞了,重疊過程如 下圖:

3.動態連接
每個棧幀都包含一個指向運行時常量池中該棧幀所屬性方法的引用,持有這個引用是為了支持方法調用過程中的動態連接。在Class文件的常量池中存有大量的 符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用為參數。這些符號引用一部分會在類加載階段或第一次使用的時候轉化為直接引用,這種轉化 稱為靜態解析。另外一部分將在每一次的運行期期間轉化為直接引用,這部分稱為動態連接。
4.方法返回地址
當一個方法被執行后,有兩種方式退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者(調 用當前方法的的方法稱為調用者),是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法方式稱為正常完成出口(Normal Method Invocation Completion)。
另外一種退出方式是,在方法執行過程中遇到了異常,並且這個異常沒有在方法體內得到處理,無論是Java虛擬機內部產生的異常,還是代碼中使用 athrow字節碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導致方法退出,這種退出方式稱為異常完成出口(Abrupt Method Invocation Completion)。一個方法使用異常完成出口的方式退出,是不會給它的調用都產生任何返回值的。
無論采用何種方式退出,在方法退出之前,都需要返回到方法被調用的位置,程序才能繼續執行,方法返回時可能需要在棧幀中保存一些信息,用來幫助恢復它的上 層方法的執行狀態。一般來說,方法正常退出時,調用者PC計數器的值就可以作為返回地址,棧幀中很可能會保存這個計數器值。而方法異常退出時,返回地址是 要通過異常處理器來確定的,棧幀中一般不會保存這部分信息。
方法退出的過程實際上等同於把當前棧幀出棧,因此退出時可能執行的操作有:恢復上層方法的局部變量表和操作數棧,把返回值(如果有的話)壓入調用都棧幀的操作數棧中,調用PC計數器的值以指向方法調用指令后面的一條指令等。
5. 附加信息
虛擬機規范允許具體的虛擬機實現增加一些規范里沒有描述的信息到棧幀中,例如與高度相關的信息,這部分信息完全取決於具體的虛擬機實現。在實際開發中,一般會把動態連接,方法返回地址與其它附加信息全部歸為一類,稱為棧幀信息。
