1. 程式人生 > >JAVA虛擬機器(JVM)——虛擬機器位元組碼執行引擎(二)

JAVA虛擬機器(JVM)——虛擬機器位元組碼執行引擎(二)

方法呼叫

     方法呼叫並不等同於方法執行,方法呼叫階段唯一的任務就是確定呼叫哪一個方法,暫時還不涉及方法內部的具體執行過程。Class檔案的編譯過程中不包含傳統編譯中的連線步驟,一切方法呼叫在Class檔案裡儲存的都只是符號引用,而不是方法在實際執行時記憶體佈局中的入口地址(相當於之前說的直接引用)。雖然這個特性給Java帶來了更強大的動態擴充套件能力,但也使Java方法呼叫過程,需要在類載入期間,甚至到執行期間才能確定目標方法的直接引用。

解析

    所有方法呼叫中的目標方法在Class檔案裡都是一個常量符號的引用,在類載入的解析階段,會將其中一部分符號引用轉化為直接引用,這種解析能成立的前提是:方法在程式真正執行之前就有一個可確定的呼叫版本,並且這個方法的呼叫版本在執行期是不可改變的。換句話說,呼叫目標在程式程式碼寫好、編譯器進行編譯時就必須確定下來。這類方法的呼叫稱為解析(Resolution)。

    靜態方法和私有方法這兩大類,前者與類直接關聯,後者在外部不可訪問,它們都適合在類載入階段進行解析。與之對應的是在Java虛擬機器裡提供了5條方法呼叫位元組碼指令:
    ·invokestatic:呼叫靜態方法
    ·invokespecial:呼叫例項構造器init方法、私有方法和父類方法
    ·invokevirtual:呼叫所以的虛方法
    ·invokeinterface:呼叫介面方法,會在執行時再確定一個實現此介面的物件。
    ·invokedynamic:先在執行時動態解析出呼叫點限定符所引用的方法,然後再執行該方法。

    只要能被invokestatic和invokespecial指令呼叫的方法,都可以在解析階段中確定唯一的呼叫版本,符合這個條件的有靜態方法、私有方法、例項構造器、父類方法四類(非虛方法,其他方法出final修飾的以外,都稱為虛方法),它們在類載入的時候就會把符號引用解析為該方法的直接引用
//方法靜態解析演示 author zzm
public class StaticResolution{

    public static void sayHello(){
        System.out.println("hello world");
    }

    public static void main(String[] args){
        StaticResolution.sayHello();
    }
}


//使用javap命令檢視這段程式的位元組碼,會發現的確是通過invokestatic指令來呼叫sayHello()方法的
D:\Develop\>javap -verbose StaticResolution
public
static void main(java.lang.String[]); Code: Stack=0, Locals=1, Args_size=1 0: invokestatic #31; //Method sayHello:()V 3: return LineNumberTable: line 15: 0 line 16: 3

分派

    解析呼叫一定是個靜態過程,在編譯期間就完全確定,在類裝載的解析階段就會把涉及的符號引用全部轉換為可用的直接引用。二分派(Dispatch)呼叫則可能是靜態的也可能是動態的,根據分派依據的宗量數可分為單分派和多分派,所以一共有4種分派組合。

靜態分派

首先通過一段程式碼演示一下什麼是靜態分派
//author zzm
public class StaticDispatch{

    static abstract class Human {}

    static class Man extends Human {}

    static class Woman extends Human {}

    public void sayHello(Human guy){
        System.out.println("hello guy");
    }

    public void sayHello(Man guy){
        System.out.println("hello gentleman");
    }

    public void sayHello(Woman guy){
        System.out.println("hello lady");
    }

    public static void main(String[] args){
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch sr = new StaticDispatch();
        sr.sayHello(man);
        sr.sayHello(woman);
    }
}

_____________________________________________________________
執行結果:

hello guy
hello guy
對於上面的程式碼,我們先定義兩個重要的概念:
    Human man = new Man();
我們把"Human"稱為變數man的靜態型別或外觀型別,而"Man"則稱為變數man的實際型別。
main()裡面的兩次sayHello()方法呼叫,使用哪個過載版本,完全取決於傳入引數的數量和資料型別。但虛擬機器(編譯器)過載時是通過引數的靜態型別而不是實際型別作為判斷依據的。並且靜態型別是編譯期可知的,因此,在編譯階段,Javac編譯器會根據引數靜態型別決定使用哪個過載版本,所以選擇了sayHello(Human)作為呼叫目標。

所有依賴靜態型別來定位方法執行版本的分派動作稱為靜態分派,靜態分派的典型應用是方法過載。

動態分派

    動態分派與重寫(Override)有著很密切的關聯,我們依舊用一段程式碼來演示動態分派。
//author zzm
public class DynamicDispatch {
    static abstract class Human{
        protected abstract void sayHello();
    }

    static class Man extends Human {
        @Override
        protected void sayHello(){
            System.out.println("man say hello");
        }
    }

    static class Woman extends Human {
        @Override
        protected void sayHello(){
            System.out.println("woman say hello");
        }
    }

    public static void main(String[] args){
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();
        man = new Woman();
        man.sayHello();
    }

}

_______________________________________________________________
執行結果:

man say hello
woman say hello
woman say hello
呼叫虛方法使用invokevirtual指令,invokevirtual指令的執行時解析過程大致分為以下幾步:
(1)找到運算元棧頂的第一個元素所指向的物件的實際型別,記作C
(2)如果在型別C中找到與常量中的描述符和簡單名稱都相符的方法,則進行訪問許可權校驗,如果通過則返回這個方法的直接引用,查詢結束;如果不通過,則返回java.lang.IllegalAccessError異常。
(3)否則,按照繼承關係從下往上依次對C類的各個父類進行第二步的搜尋和驗證過程。
(4)如果最終沒找到合適的方法,則丟擲java.lang.AbstractMethodError異常。

由於invokevirtual指令執行的第一步就是在執行期間確定接收者的實際型別,所以兩次呼叫中的invokevirtual指令把常量池中的類方法符號引用解析到了不同的直接引用上,這個過程就是java語言中重寫的本質。我們把這種在執行期根據實際型別確定方法執行版本的分派過程稱為動態分派。

單分派與多分派

方法的接收者與方法的引數統稱為方法的宗量。根據分派基於多少種宗量,可以將分派劃分成單分派和多分派兩種。至今,java語言是“靜態多分派,動態單分派”的。下面通過一段程式碼來解釋一下:
//author zzm
public class Dispatch {

    static class QQ {}

    static class _360 {}

    public static class Father {
        public void hardChoice(QQ arg) {
            System.out.println("father choose QQ");
        }

        public void hardChoice(_360 arg) {
            System.out.println("father choose 360");
        }
    }

    public static class Son extends Father {
        public void hardChoice(QQ arg) {
            System.out.println("son choose QQ");
        }

        public void hardChoice(_360 arg) {
            System.out.println("son choose 360");
        }
    }

    public static void main(String[] args) {        
        Father father = new Father();
        Father son = new Son();
        father.hardChoice(new _360());
        son.hardChoice(new QQ());
    }
}
______________________________________________________
執行結果:

father choose 360
son choose QQ
我們先看編譯階段編譯器的選擇過程,也就是靜態分派的過程。這是選擇目標方法的依據有兩個:一是靜態型別是Father還是Son,二是方法引數是QQ還是_360,所以這是根據兩個宗量進行選擇,即靜態多分派。

再看執行階段虛擬機器的選擇,也就是動態分派過程。由於編譯器已經確定引數的型別,所以此時只需要確定接收者的實際型別是Father還是Son。所以只有一個宗量作為選擇依據,即動態單分派。

相關推薦

JAVA虛擬機器JVM——虛擬機器位元組執行引擎

方法呼叫 方法呼叫並不等同於方法執行,方法呼叫階段唯一的任務就是確定呼叫哪一個方法,暫時還不涉及方法內部的具體執行過程。Class檔案的編譯過程中不包含傳統編譯中的連線步驟,一切方法呼叫在C

深入理解Java虛擬機器(七)位元組執行引擎棧幀、動態連線、方法呼叫

執行引擎是Java虛擬機器最核心的組成部分之一。“虛擬機器”是一個相對於“物理機”的概念,這兩種機器都有程式碼執行能力,其區別是物理機的執行引擎是直接建立在處理器、硬體、指令集和作業系統層面上的,而虛擬機器的執行引擎則是由自己實現的,因此可以自行制定指令集與執行引擎的結構體系,並且能夠執

JVM十一:虛擬機器位元組執行引擎2

解析      繼續前面關於方法呼叫的話題,所有方法呼叫中的目標方法在 Class 檔案裡面都是一個常量池中的符號引用,在類載入的解析階段,會將其中的一部分符號引用轉化為直接引用,這種解析能成立的前提是:方法在程式真正執行之前就有一個可確定的呼叫版本,並且

JVM十一:虛擬機器位元組執行引擎1

         執行引擎是Java最核心的組成部分之一。虛擬機器與物理機的區別:虛擬機器是一個相對“物理機”的概念,這兩種機器都有程式碼執行能力,其區別就是物理機的執行引擎是直接建立在處理器,硬體,指令集和作業系統層面

JVM虛擬機器位元組執行引擎

虛擬機器的執行引擎是自己實現的,有自己的指令集和執行引擎的結構體系,能夠執行那些不被硬體直接支援的指令集格式。(物理機執行引擎是建立在處理器、硬體、指令集和作業系統層面)。 但在不同的虛擬機器實現裡,執行引擎在執行java程式碼的時候,可能會解釋執行和編譯執行

虛擬機器位元組執行引擎幀棧執行結構、方法呼叫分派

執行時幀棧結構 幀棧是在虛擬機器棧中的棧元素,每個幀棧包含區域性變量表、運算元棧、動態連線、方法返回地址和一些額外資訊。編譯時,幀棧需要多大區域性變量表,運算元棧多深都已確定,且分配了記憶體,不會受到執行期變數資料的影響。對執行引擎來說,活動執行緒中只有棧頂的棧幀是有效的,

深入理解Java虛擬機器類檔案結構+類載入機制+位元組執行引擎

周志明的《深入理解Java虛擬機器》很好很強大,閱讀起來頗有點費勁,尤其是當你跟隨作者的思路一直探究下去,開始會讓你弄不清方向,難免有些你說的啥子的感覺。但知識不得不學,於是天天看,反覆看,就慢慢的理解了。我其實不想說這種硬磨的方法有多好,我甚至不推薦,我建議大家閱讀這本書時,由淺入深,有舍有得,先從巨集觀去

jdk原始碼解析——Java虛擬機器位元組執行引擎

在前面我們瞭解了jvm執行時資料區,那個jvm圖中有執行引擎,那麼今天就解釋一下Java虛擬機器位元組碼執行引擎。 1 定義 Java虛擬機器位元組碼執行引擎是jvm最核心的組成部分之一,“虛擬機器” 是一個相對於 “物理機” 的概念,這兩種機器都有程式碼執行能力,其區別是物理機的執行

Java虛擬機器學習筆記位元組執行引擎

執行時棧幀結構 1.區域性變量表 null JIT編譯器優化 2.運算元棧 LIFO 3.動態連結 | 4.方法返回地址 | 棧幀資訊 5.附加資訊 | —————————————————————————————————— 方法呼叫 1.解析呼叫 符號引用 靜態、私有

深入理解Java虛擬機器----位元組執行引擎

    位元組碼執行引擎是執行引擎是最重要的一部分,概念模型的總體外觀是一致的:輸入位元組碼,過程是位元組碼解析的等效過程,輸出結果。不同的虛擬機器有不同的具體實現,大體有解釋執行和編譯執行兩種選擇。 執行時棧幀結構:     棧楨在虛擬機器棧中,是支援方法呼叫和執行的結

簡述JVM基礎虛擬機器位元組執行引擎

作者 | 井方哥地址 | https://zhuanlan.zhihu.com/p/312352

深入理解jvm虛擬機器位元組執行引擎

執行時棧幀 每一個方法從呼叫開始到執行完成都對應著一張棧幀的進棧和出棧。棧幀中儲存著區域性變量表,運算元表,動態連結和方法返回地址。位於虛擬機器最頂層的稱為當前方法棧。 區域性變量表 儲存當前方法的區域性變數和引數,區域性變量表的容量以變數槽slo

深入理解Java虛擬機器位元組執行引擎

本文首發於微信公眾號:BaronTalk 執行引擎是 Java 虛擬機器最核心的組成部分之一。「虛擬機器」是相對於「物理機」的概念,這兩種機器都有程式碼執行的能力,區別是物理機的執行引擎是直接建立在處理器、硬體、指令集和作業系統層面上的,而虛擬機器執行引擎是由自己實現的,因此可以自行制定指令集與執行

【深入Java虛擬機器】之七:深入JVM位元組執行引擎

我們都知道,在當前的Java中(1.0)之後,編譯器講原始碼轉成位元組碼,那麼位元組碼如何被執行的呢?這就涉及到了JVM的位元組碼執行引擎,執行引擎負責具體的程式碼呼叫及執行過程。就目前而言,所有的執行引擎的基本一致: 輸入:位元組碼檔案 處理:位元組碼解析 輸出:執

讀書筆記 ---- 《深入理解Java虛擬機器》---- 第7篇:虛擬機器位元組執行引擎

上一篇:虛擬機器類載入機制:https://blog.csdn.net/pcwl1206/article/details/84260914 第7篇:虛擬機器位元組碼執行引擎 執行引擎是Java虛擬機器最核心的組成部分之一。“虛擬機器”是一個相對於“物理機”的概念,這兩種機器都有程式碼執行能力

深入理解JVM虛擬機器讀書筆記【第八章】虛擬機器位元組執行引擎

8.1 概述 8.2 執行時棧幀結構 8.2.1 區域性變量表 8.2.2 運算元棧 8.2.3 動態連線 8.2.4 方法返回地址

【深入理解 Java 虛擬機器筆記】虛擬機器位元組執行引擎

7.虛擬機器位元組碼執行引擎 執行引擎是 Java 虛擬機器最核心的組成部分之一。在 Java 虛擬機器規範中制定了虛擬機器位元組碼執行引擎的概念模型,這個概念模型成為各種虛擬機器執行引擎的統一外觀(Facade)。不同的虛擬機器實現,執行引擎可能會有解釋執行和編譯執行兩種,有可能兩

深入理解JVM虛擬機器(七):虛擬機器位元組執行引擎

程式碼編譯的結果就是從本地機器碼轉變為位元組碼。我們都知道,編譯器將Java原始碼轉換成位元組碼?那麼位元組碼是如何被執行的呢?這就涉及到了JVM位元組碼執行引擎,執行引擎負責具體的程式碼呼叫及執行過程。就目前而言,所有的執行引擎的基本一致: 輸入:位元組碼檔案

虛擬機器位元組執行引擎 JVM筆記4

目錄   概述 執行時棧幀結構 區域性變量表 運算元棧 方法返回地址 附加資訊 方法呼叫 解析 分派 靜態分派 動態分派 單分派與多分派 虛擬機器動態分派的實現 基於棧的位元組碼解釋執行引擎 概述 輸入的是位元組

深入理解java虛擬機器虛擬機器位元組執行引擎

引言 呼叫一個方法就會將該方法(將方法的各種資訊封裝成棧幀)進行壓棧,方法結束就會出棧。而棧幀是支援方法呼叫和執行的基礎結構,是虛擬機器棧的元素,棧幀儲存了一個方法的區域性變量表,運算元棧,動態連線和方法返回地址資訊等其他資訊。在編譯時就已經確定好了區域性變量