java虛擬機:虛擬機棧

1、引文

   前文中說到:「虛擬機棧是線程私有的,每建立一個線程,虛擬機就會爲這個線程建立一個虛擬機棧虛擬機棧表示Java方法執行的內存模型,每調用一個方法就會爲每一個方法生成一個棧幀(Stack Frame),用來存儲局部變量表、操做數棧、動態連接、方法出口等信息。每一個方法被調用和完成的過程,都對應一個棧幀從虛擬機棧上入棧和出棧的過程。虛擬機棧的生命週期和線程是相同的」。html

2、棧結構圖解

    其中,虛擬機棧是一個後入先出的棧。棧幀是保存在虛擬機棧中的,棧幀是用來存儲數據和存儲部分過程結果的數據結構,同時也被用來處理動態連接(Dynamic Linking)、方法返回值和異常分派(Dispatch Exception)。線程運行過程當中,只有一個棧幀是處於活躍狀態,稱爲「當前活躍棧幀」,當前活動棧幀始終是虛擬機棧的棧頂元素。以下圖所示:數據結構

 棧流程

上述內容已對棧幀作了大體介紹,接下去仔細描述棧幀中的操做數棧,動態鏈接,方法返回地址和一些額外的附加信息。 以下圖所示:

棧幀詳情

3、棧幀詳解

1.局部變量表

    局部變量表是一組局部變量值存儲空間,用於存放方法參數和方法內部定義的局部變量在Java文件編譯爲Class文件時,就在方法表的Code屬性的max_locals數據項中肯定了該方法須要分配的最大局部變量表的容量post

2.操做數棧

    操做數棧也常被稱爲操做棧,它是一個後入先出棧。JVM底層字節碼指令集是基於棧類型的,全部的操做碼都是對操做數棧上的數據進行操做,對於每個方法的調用,JVM會創建一個操做數棧,以供計算使用。和局部變量同樣。操做數棧的最大深度也是編譯的時候寫入到方法表的code屬性的max_stacks數據項中。操做數棧的每個元素能夠是任意的Java數據類型,包括long、double。32位數據類型所佔的棧容量爲1,64位數據類型所佔的棧容量爲2。棧容量的單位爲「字寬」。當一個方法剛剛執行的時候,這個方法的操做數棧是空的,在方法執行的過程當中,會有各類字節碼指向操做數棧中寫入和提取值,也就是入棧與出棧操做。例如,在作算術運算的時候就是經過操做數棧來進行的,又或者調用其它方法的時候是經過操做數棧來行參數傳遞的。 另外,在概念模型中,兩個棧幀做爲虛擬機棧的元素,相互之間是徹底獨立的,可是大多數虛擬機的實現裏都會做一些優化處理,令兩個棧幀出現一部分重疊。讓下棧幀的部分操做數棧與上面棧幀的部分局部變量表重疊在一塊兒,這樣在進行方法調用返回時就能夠共用一部分數據,而無須進行額外的參數複製傳遞了優化

3.動態鏈接

    每一個棧幀都包含一個指向運行時常量池中該棧幀所屬性方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。在Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用爲參數。這些符號引用一部分會在類加載階段或第一次使用的時候轉化爲直接引用,這種轉化稱爲靜態解析。另一部分將在每一次的運行期期間轉化爲直接引用,這部分稱爲動態鏈接spa

4.方法返回地址

    當一個方法被執行後,有兩種方式退出這個方法。第一種方式是執行引擎遇到任意一個方法返回的字節碼指令,這時候可能會有返回值傳遞給上層的方法調用者(調用當前方法的的方法稱爲調用者),是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定,這種退出方法方式稱爲正常完成出口(Normal Method Invocation Completion)。另一種退出方式是,在方法執行過程當中遇到了異常,而且這個異常沒有在方法體內獲得處理,不管是Java虛擬機內部產生的異常,仍是代碼中使用throw字節碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會致使方法退出,這種退出方式稱爲異常完成出口(Abrupt Method Invocation Completion)。一個方法使用異常完成出口的方式退出,是不會給它的調用都產生任何返回值的。     不管採用何種方式退出,在方法退出以前,都須要返回到方法被調用的位置,程序才能繼續執行,方法返回時可能須要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。通常來講,方法正常退出時,調用者PC計數器的值就能夠做爲返回地址,棧幀中極可能會保存這個計數器值。而方法異常退出時,返回地址是要經過異常處理器來肯定的,棧幀中通常不會保存這部分信息。 方法退出的過程實際上等同於把當前棧幀出棧,所以退出時可能執行的操做有:恢復上層方法的局部變量表和操做數棧,把返回值(若是有的話)壓入調用都棧幀的操做數棧中,調用PC計數器的值以指向方法調用指令後面的一條指令等。線程

4、方法執行過程

   虛擬機棧的棧元素是棧幀,當有一個方法被調用時,表明這個方法的棧幀入棧;當這個方法返回時,其棧幀出棧。所以,虛擬機棧中棧幀的入棧順序就是方法調用順序。什麼是棧幀呢?棧幀能夠理解爲一個方法的運行空間。它主要由兩部分構成,一部分是局部變量表,方法中定義的局部變量以及方法的參數就存放在這張表中;另外一部分是操做數棧,用來存放操做數。咱們知道,Java 程序編譯以後就變成了一條條字節碼指令,其形式相似彙編,但和彙編有不一樣之處:彙編指令的操做數存放在數據段和寄存器中,可經過存儲器或寄存器尋址找到須要的操做數;而 Java 字節碼指令的操做數存放在操做數棧中,當執行某條帶 n 個操做數的指令時,就從棧頂取 n 個操做數,而後把指令的計算結果(若是有的話)入棧。所以,當咱們說 JVM 執行引擎是基於棧的時候,其中的「棧」指的就是操做數棧。舉個簡單的例子對比下彙編指令和 Java 字節碼指令的執行過程,好比計算 1 + 2,在彙編指令是這樣的:code

mov ax, 1 ;把 1 放入寄存器 ax
add ax, 2 ;用 ax 的內容和 2 相加後存入 ax

 而 JVM 的字節碼指令是這樣的:orm

iconst_1 //把整數 1 壓入操做數棧
iconst_2 //把整數 2 壓入操做數棧
iadd //棧頂的兩個數相加後出棧,結果入棧

因爲操做數棧是內存空間,因此字節碼指令沒必要擔憂不一樣機器上寄存器以及機器指令的差異,從而作到了平臺無關。htm

注意,局部變量表中的變量不可直接使用,如需使用必須經過相關指令將其加載至操做數棧中做爲操做數使用。好比有一個方法 void foo(),其中的代碼爲:int a = 1 + 2; int b = a + 3;,編譯爲字節碼指令就是這樣的:blog

iconst_3 //把整數 1 壓入操做數棧
iconst_2 //把整數 2 壓入操做數棧
iadd //棧頂的兩個數出棧後相加,結果入棧;實際上前三步會被編譯器優化爲:iconst_3
istore_1 //把棧頂的內容放入局部變量表中索引爲 1 的 slot 中,也就是 a 對應的空間中
iload_1 // 把局部變量表索引爲 1 的 slot 中存放的變量值(3)加載至操做數棧
iconst_3 
iadd //棧頂的兩個數出棧後相加,結果入棧
istore_2 // 把棧頂的內容放入局部變量表中索引爲 2 的 slot 中,也就是 b 對應的空間中
return // 方法返回指令,回到調用點

 

須要說明的是,局部變量表以及操做數棧的容量的最大值在編譯時就已經肯定了,運行時不會改變。而且局部變量表的空間是能夠複用的,例如,當指令的位置超出了局部變量表中某個變量 a 的做用域時,若是有新的局部變量 b 要被定義,b 就會覆蓋 a 在局部變量表的空間。

 

原文地址:http://www.cnblogs.com/Codenewbie/p/6184898.html 
相關文章
相關標籤/搜索