JVM學習筆記——節碼執行引擎

簡述

執行引擎是Java虛擬機最核心的組成部分之一,,全部的Java虛擬機的執行引擎都是一致的:
輸入:字節碼文件
處理:字節碼解析
輸出:執行結果java

運行時棧幀結構

在介紹虛擬機棧時就提到,每一個方法在執行的同時都會建立一個棧幀用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。棧幀中須要多大的局部變量表和多深的操做數棧在編譯代碼的過程當中已經徹底肯定,並寫入到方法表的Code屬性中。在活動的線程中,位於當前棧頂的棧幀纔是有效的,執行引擎運行的全部字節碼指令只針對當前棧幀進行操做。jvm


方法調用

一切方法調用在Class文件裏面都是一個常量池中的符號引用。在類加載解析階段,可能會將其中一部分符號引用轉化爲直接引用,也有可能會在初始化階段以後再開始,取決於方法在運行以前是否有有肯定的調用版本,且在運行期間不變。

在Java中符合編譯器可知,運行期不可變的方法,主要包括靜態方法和私有方法兩大類,它們都不可能經過某些方式重寫其餘版本,所以它們都適合在類加載階段進行解析

JVM提供了5條方法調用字節碼:
invokestatic:調用靜態方法
invokespecial:調用實例構造器方法、私有方法和父類方法
invokevirtual:調用全部的虛方法
invokedynamic:運行時動態解析出調用點限定符所引用的方法,再執行該方法
只要能被invokestaticinvokespecial指令調用的方法,均可以在解析階段中肯定惟一的調用版本,它們在類加載時會把符號引用解析爲該方法的直接引用。

示例:
spa

  • invokestatic
  • public class Person {
        public static void sayHello() {
            System.out.println("hello world");
        }
        public static void main(String[] args) {
            sayHello();
        }
    }
    複製代碼
    javap -verbose查看


  • invokespecial
  • public class Person {
        public static void main(String[] args) {
            new Person();
        }
    }
    複製代碼
    javap -verbose查看

    經過invokestatic和invokespecial指令調用的方法稱爲非虛方法,與之相反,其餘方法稱爲虛方法(除去final方法),虛方法的調用是一個分派的過程,有靜態也有動態,可分爲靜態單分派、靜態多分派、動態單分派和動態多分派。線程

    靜態分派

    全部依賴靜態類型來定位方法執行版本的分派,靜態分派的典型應用是方法重載,靜態分派發生在編譯階段,所以靜態分派的動做不禁虛擬機執行。eg:3d

    public class Person {
        public void sayHello(Object obj){
            System.out.println("hello Object");
        }
        public void sayHello(String str){
            System.out.println("hello String");
        }
        public static void main(String[] args) {
            Person p = new Person();
            Object obj = new String();
            p.sayHello(obj);
        }
    }
    複製代碼

    結果:code

    hello Object
    複製代碼

    相對於變量obj,Object是其靜態變量,String是其實際變量,在編譯階段,Java編譯器會根據參數的靜態類型決定調用哪一個重載版本。用javap -verbose再看下
    cdn

    動態分派

    動態分派根據實際類型肯定方法執行版本對象

    public class Person {
        public static void main(String[] args) {
            Object p = new Person();
            Object obj = new String("比利時");
            System.out.println(obj.toString());
            System.out.println(p.toString());
        }
    }
    複製代碼

    輸出blog

    比利時
    jvm.Person@63ce0e18
    複製代碼

    同一個靜態類型,調用toString方法,結果徹底不一樣,緣由就是由於這兩個變量的實際類型不一樣。經過java -verbose 查看字節碼指令
    繼承

    2一、31行指令將以前存放到局部變量表一、2位置的對象引用(接受者)壓入操做數棧的棧頂,2二、32行是方法調用指令,雖然指令同樣都是Object.toString,可是這兩個指令最終執行的目標方法不相同
    ①.找到操做數棧的棧頂元素所指向的對象的實際類型,記爲C
    ②.若是C中存在描述符和簡單名稱都相符的方法,則進行訪問權限驗證,若是驗證經過,則直接返回這個方法的直接引用,不然返回java.lang.IllegalAccessError異常
    ③.若是C中不存在對應的方法,則按照繼承關係對C的各個父類進行第2步的操做
    ④.若是各個父類也沒對應的方法,則拋出異常
    因此上述兩次invokevirtual指令將相同的符號引用解析成了不一樣對象的直接引用,這個過程就是Java語言中重寫的本質

    如何實現?
    爲類在方法區中創建虛方法表,虛方法表中存放着各個方法的實際入口地址,若是某個方法在子類中沒有被重寫,那子類的虛方法表中的地址入口和父類相同方法的地址入口一致,都指向父類的實現入口。

    以上面代碼爲例,Person類沒有重寫toString()方法,因此toString()方法指向Object類型數據,而String重寫了toString()方法,因此沒有( 未列Object其餘方法)。

    感謝

    《深刻理解Java虛擬機》

    相關文章
    相關標籤/搜索