Java虛擬機-虛擬機字節碼執行引擎

1.運行時棧幀結構
       棧幀也叫過程活動記錄,是編譯器用來進行方法調用和方法執行的一種數據結構,它是虛擬機運行時數據區域中的虛擬機棧的棧元素。棧幀中包括局部變量表,操作數棧,動態鏈接和方法返回地址以及額外的一些附加信息,在編譯過程中,局部變量表的大小已經確定,操作數棧深度也已經確定,因此棧幀在運行的過程中需要分配多大的內存是固定的,不受運行時影響。對於沒有逃逸的對象也會在棧上分配內存,對象的大小其實在運行時也是確定的,因此即使出現了棧上內存分配,也不會導致棧幀改變大小。一個線程中,可能調用鏈會很長,很多方法都同時處於執行狀態。對於執行引擎來講,活動線程中,只有棧頂的棧幀是最有效的,稱爲當前棧幀,這個棧幀所關聯的方法稱爲當前方法。執行引擎所運行的字節碼指令僅對當前棧幀進行操作。

                                               

2.局部變量表
       方法中的參數和局部變量都存在局部變量表中,局部變量如果在聲明時未初始化則無法使用,局部變量表中以32位slot爲單位進行存儲,slot可以複用但對垃圾收集會有些影響。slot複用,當一個變量的pc寄存器的值大於slot的作用域的時候,slot是可以複用的。下面通過代碼解釋一下slot複用對垃圾回收的影響。

運行下面的代碼:

public class TestDemo {
    public static void main(String[] args) {
        byte[] buffer = new byte[60 * 1024 * 1024];
        System.gc();
    }
}

結果如下圖,我們發現即使我們顯示調用了垃圾回收,實際上並沒有執行垃圾回收。

再運行下面的代碼:

public class TestDemo {
    public static void main(String[] args) {
        {
            byte[] buffer = new byte[60 * 1024 * 1024];
        }
        System.gc();
    }
}

結果如下圖,我們發現即使buffer出了作用域也沒有被回收,這是因爲對slot沒有讀寫操作,虛擬機感受不到slot要被複用。

再運行下面代碼:

public class TestDemo {
    public static void main(String[] args) {
        {
            byte[] buffer = new byte[60 * 1024 * 1024];
        }
        int a = 0;
        System.gc();
    }
}

結果如下圖,進行了垃圾回收,這是因爲有新定義的變量,虛擬機會掃描有沒有可以複用的slot,如果發現有大於slot作用域的變量就進行垃圾回收。

再看下面的代碼:

public class TestDemo {
    public static void main(String[] args) {
        byte[] buffer = new byte[60 * 1024 * 1024];
        buffer = null;
        System.gc();
    }
}

結果如下圖,進行了垃圾回收,所以建議當一個變量不再使用時,將其置爲null,幫助進行垃圾回收。

3.操作數棧
       Java虛擬機的解釋執行引擎被稱爲“基於棧的執行引擎”,其中所致的棧就是指-操作數棧。操作數棧也常被稱爲操作棧。和局部變量區一樣,操作數棧也是被組織成一個以字長爲單位的數組。但是和前者不同的是,它不是通過索引來訪問,而是通過標準的棧操作-壓棧和出棧來訪問的。比如,如果某個指令把一個值壓入到操作數棧中,稍後另一個指令就可以彈出這個值來使用。
虛擬機在操作數棧中存儲數據的方式和局部變量區中的是一樣的:如int、long、floag、double、reference的存儲。對於byte、short以及char類型的值在壓入到操作數棧之前,也會被轉換爲int。
       虛擬機把操作數棧作爲它的工作區,大多數指令都要從這裏彈出數據,執行運算,然後把結果壓回操作數棧。比如,iadd指令就要從操作數棧中彈出兩個證書,執行加法運算,其結果又壓回操作數棧中,下面是一個例子,演示了虛擬機如何把兩個int類型的局部變量相加,再把結果保存到第三個局部變量的:
      iload_0     // push the int int local variable 0 onto the stack
      iload_1    // push the int local variable 1 onto the stack
      iadd        // pop two ints,add them,push result
      istore_2 // pop int,store into local variable 2

       在這個字節碼序列裏,前兩個指令iload_0和iload_1將存儲在局部變量中索引爲0和1的整數壓入操作數棧中,其中iadd指令從操作數棧中彈出那兩個整數相加,再將結果壓入操作數棧。第四條指令,istore_2則從操作數棧中彈出結果,並把它存儲到局部變量區索引爲2的位置。下圖表示了這個過程中局部變量表和操作數棧的狀態變化:

      

4.動態鏈接
       靜態鏈接是指在類加載或第一次引用的時候就會轉化爲直接引用即地址引用。在每一次運行期間都會轉化爲地址引用,棧幀中都包含一個指向運行時常量池中該棧幀所屬方法的引用。有了這個引用,就可以支持方法調用的動態鏈接。

5.方法返回地址和附加信息
       方法調用時通過一個指向方法的指針指向方法的地址,方法返回時將回歸到調用處,那個方法是返回地址。正常返回通過pc寄存器的值,異常返回通過異常表。虛擬機規範中允許具體的虛擬機實現增加一些規範裏沒有描述的信息到棧幀中。這部分信息完全取決於虛擬機的實現。

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