通過上一篇文章的介紹我們了解了JVM中數據類型以及數據區的知識,這篇我們會通過對JVM堆棧的幀的詳細介紹了解方法執行的一些內幕。
幀通常用於存儲數據和部分結果,同時還用於執行動態鏈接、返回方法的返回值以及分發異常。
幀在方法調用的時候被創建,在方法完成的時候銷毀。它是在創建它的線程的JVM堆棧中分配到空間的,每個幀都有它自己的局部變量數組、操作數堆棧和一個當前方法所在的類的運行時常量池的引用。
它的局部變量數組和操作數堆棧的大小是在編譯的時候就確定了的,而且它是和它所聯系的方法的代碼一起提供的,因此它的數據結構的尺寸僅僅依賴於JVM的實現和方法調用時同時可以分配的內存。
對於正在執行的方法而言只有一個幀是活動的,這個幀就是所謂的當前幀,它的方法就是當前方法,當前方法所在的類被定義為當前類。局部變量和操作數堆棧的操作通常和當前幀有關。
如果一個幀所在的方法調用了另外的方法或者方法結束,那麼該幀不再是當前幀。如果是調用另外的方法,那麼一個新的幀會被創建並且在控制權轉換到新方法時成為當前幀;如果是方法結束,如果有方法返回,當前幀將它的方法調用的結果傳遞給前一個幀,當前一個幀成為當前幀時當前幀被丟棄。
需要注意的是由一個線程創建的幀是局部於該線程的,其它的線程不能引用它。
每個幀都包含變量數組,也就是我們所熟知的局部變量數組。一個局部變量可以保存一個boolean、 byte、char、short、int、float、引用或者returnAddress值,一對局部變量才能保存一個long或者double值。
局部變量是根據索引進行尋址的,第一個局部變量的索引是0。如果一個整型值介於0和局部變量數組的長度之間並且也只有在這個區間的時候它才會被作為局部變量數組的索引。
long型或者double型的值占用兩個連續的局部變量,這樣的值可能只能使用較小的那個索引值進行尋址,例如,局部變量數組中索引為n的double變量值實際上占用n和n+1,但是局部變量n+1是不能讀取的,它可以被寫入,但是這樣做會使得局部變量n的內容無效。JVM沒有要求n是偶數,這就意味著double和long型值在局部變量數組中不必是64位對齊的,JVM的實現者可以決定使用適當的方式表示那樣的值。
JVM使用局部變量傳遞方法調用的參數,對於類方法調用(也就是static方法),所有的參數都是連續的存儲在局部變量表中並且是從0開始的,對於實例方法調用,所有的參數也是連續的但是是從1開始的,局部變量0存儲的是實例方法所在的類實例的引用。
每個幀都包含一個後進先出的堆棧,也就是它的操作數堆棧。
操作數堆棧在剛剛被創建的時候是空的,JVM提供指令從局部變量或者成員加載常量或者值到堆棧,其它的JVM指令從操作數堆棧提取操作數,操作它們並將結果放回操作數堆棧。操作數堆棧也用於准備傳遞給方法的參數以及接收方法的結果。
例如一個iadd指令將兩個int值相加,該指令要求它的前一條指令將它要相加的兩個值壓入操作數堆棧的最上面,它從操作數堆棧取出那兩個值進行相加並將結果放回操作數堆棧。
子計算可能是嵌套在操作數堆棧中的,產生的值可以被嵌入的計算使用。
操作數堆棧的每一項都可以保存JVM的任何類型的值,包括long和double型的。
操作數堆棧中的值必須根據其類型進行操作。下面的這些情況都是不可能的:壓入兩個int值而後續的操作將它們作為long型或者壓入兩個float值而後續的操作是iadd指令(該指令的操作對象是兩個int型)。有一小部分JVM指令(例如dup和swap)將運行時數據區的值作為原始的值(raw value)進行操作而不考慮其類型,這些指令是以一種不能用於修改或者分解單獨的值的方式定義的,這些對操作數堆棧操作的限制通過類文件驗證進行了強制。
在任何時候操作數堆棧都有其相應的深度,long或者double型的值是兩個單位而其它的值是一個單位。
每個幀都包含一個相應於當前方法的類型的運行時常量池的引用以支持方法代碼的動態鏈接。類文件代碼中的方法代碼指的是被調用的方法以及通過符號引用可以訪問的變量,動態鏈接將這些符號方法引用翻譯為具體的方法引用、在必要的時候加載類以解析未定義的符號以及將變量訪問翻譯為那些變量的運行時位置在存儲結構中的適當的偏移。方法和變量的晚期綁定使得方法使用到的其它類的變化可以破壞該代碼的可能性更小。
如果方法調用沒有導致一個異常(無論是JVM拋出的還是代碼顯式拋出的)就被認為是方法調用正常結束。如果當前方法調用正常結束,那麼一個值可能被返回給調用它的方法。
在這種情況下,當前幀被用於恢復調用者的狀態,包括它的局部變量和操作數堆棧以及適當增加程序計數器以跳過方法調用指令。方法調用者所在的幀的程序的執行正常的繼續,如果有方法返回,返回值被壓入幀的操作數堆棧。
如果方法裡面的一個JVM指令的執行引起JVM拋出一個異常並且那個異常在方法裡面沒有被處理就會導致方法調用突然結束,執行一個athrow指令也可以導致一個異常被顯式的拋出並且如果那個異常沒有被當前方法捕獲也可以導致方法調用突然結束,一個突然結束的方法調用永遠也不會向它的調用者返回一個值。
一個幀可能會被像調試信息這樣的與實現相關的特定信息擴展。