1. 程式人生 > >虛擬機器位元組碼執行過程。

虛擬機器位元組碼執行過程。

虛擬機器位元組碼執行引擎:概述,執行引擎:是jvm最核心的部分之一。和物理機相對應。物理機是指直接建立在處理器,硬體,指令集,作業系統層面。虛擬機器是自己實現的,自己制定結構體系。

執行過程:
1. 輸入位元組碼檔案
2. 處理位元組碼
3. 輸出結果。

執行依靠資料結構-棧幀(stack-frame)存放:
1. 區域性變量表
2. 運算元棧
3. 動態連結
4. 方法返回地址
5. 額外的附加資訊

在編譯期已經確定區域性變量表和運算元棧的大小,分配空間已經確定,不會隨著程式的執行而改變。
程式執行期,可能會含有多個執行緒,每個執行緒有多個棧幀,在活動執行緒裡面,只有棧頂的棧幀是有效的。
關於區域性變量表:
存放方法引數和變數,在編譯器,就在方法Code屬性的max_locals資料項裡面分配了空間。區域性變量表的最小的單位是:變數槽(Variable Slot)一個solt佔用32位空間,一個solt可以存放一個32位資料型別。當遇到64位的資料型別時,會分配兩個連續的solt空間。

Solt的複用和GC的關係:
當變數被分配的時候,不會立即引發GC,會在新的資料型別複用原來的記憶體空間後,會進行垃圾回收。
關於運算元棧:
運算元棧:進行運算元的運算。
關於動態連結:
在類載入時期,符號引用會轉換為直接引用稱為靜態解析。
在執行期,直接轉化為直接引用稱為動態連線。
關於方法返回地址:
1. 正常完成出口:JVM遇到一個方法返回的位元組碼出口。
2. 異常退出。

方法呼叫:
呼叫的是一個class檔案的一個符號引用。
1. invokestatic:呼叫靜態方法;
2. invokespecial:呼叫例項構造方法,私有方法和父類方法;
3. invokevirtual:呼叫虛方法和final修飾的方法。
4. invokeinterface:呼叫介面方法,在執行時再確定一個實現此介面的物件;
5. invokedynamic:在執行時動態解析出呼叫點限定符所引用的方法之後,呼叫該方法;
通過invokestatic和invokespecial指令呼叫的方法,可以在解析階段確定唯一的呼叫版本,符合這種條件的有靜態方法、私有方法、例項構造器和父類方法4種,它們在類載入時會把符號引用解析為該方法的直接引用。

靜態分派:

public class StaticDispatch {
    static abstract class Humnan {}
    static class Man extends Humnan {}
    static class Woman extends Humnan {}
    public void hello(Humnan guy) {
        System.out.println("hello, Humnan");
    }

    public void hello(Man guy) {
        System.out.println("hello, Man"
); } public void hello(Woman guy) { System.out.println("hello, Woman"); } public static void main(String[] args) { Humnan man = new Man(); Humnan woman = new Woman(); StaticDispatch dispatch = new StaticDispatch(); dispatch.hello(man); dispatch.hello(woman); } } hello, Humnan hello, Humnan

通過位元組碼指令,可以發現兩次hello方法都是通過invokevirtual指令進行呼叫,而且呼叫的是引數為Human型別的hello方法。

動態分派(重寫):

public class DynamicDispatch {
    static abstract class Humnan {
        abstract void say();
    }
    static class Man extends Humnan {
        @Override
        void say() {
            System.out.println("hello, Man");
        }
    }
    static class Woman extends Humnan {
        @Override
        void say() {
            System.out.println("hello, Woman");
        }
    }

    public static void main(String[] args) {
        Humnan man = new Man();
        Humnan woman = new Woman();
        man.say();
        woman.say();
    }
}
hello, Man
hello, Woman

invokevirtual指令的多型查詢,invokevirtual指令在執行時分為以下幾個步驟:
1. 找到運算元棧的棧頂元素所指向的物件的實際型別,記為C;
2. 如果C中存在描述符和簡單名稱都相符的方法,則進行訪問許可權驗證,如果驗證通過,則直接返回這個方法的直接引用,否則返回java.lang.IllegalAccessError異常;
3. 如果C中不存在對應的方法,則按照繼承關係對C的各個父類進行第2步的操作;
4. 如果各個父類也沒對應的方法,則返回異常;
所以上述兩次invokevirtual指令將相同的符號引用解析成了不同物件的直接引用,這個過程就是Java語言中重寫的本質。

JVM動態分派實現
由於動態分派是非常頻繁的動作,因此在虛擬機器的實際實現中,會基於效能的考慮,並不會如此頻繁的搜尋對應方法,一般會在方法區中建立一個虛方法表,使用虛方法表代替方法查詢以提高效能。
虛方法表在類載入的連線階段進行初始化,存放著各個方法的實際入口地址,如果某個方法在子類中沒有被重寫,那麼子類的虛方法表中該方法的入口地址和父類保持一致。

基於Stack的位元組碼解釋執行引擎:
直譯器執行。
程式在JVM裡面的實現過程(基於stack)。
例如:
如果有多個變數宣告,會先把變數push進入操作棧,接著istore指令會將操作棧裡面的變數放入區域性變量表。接著進行運算,最後返回方法函式值。