天天看點

java怎麼調用派_JVM 方法調用之動态分派(詳解)

1. 動态分派

一個展現是重寫(override)。下面的代碼,運作結果很明顯。

public class App {

public static void main(String[] args) {

Super object = new Sub();

object.f();

}

}

class Super {

public void f() {

System.out.println("super : f()");

}

public void f(int i) {

System.out.println("super : f(int)");

}

}

class Sub extends Super{

@Override

public void f() {

System.out.println("sub : f()");

}

@Override

public void f(int i) {

System.out.println("sub : f(int)");

}

public void f(char c) {

System.out.println("sub : f(char)");

}

}

最終輸出sub : f();

那麼虛拟機是怎麼做到動态分派的呢?

不同的虛拟機有不同的實作,最常用的是使用虛方法表(Virtual Method Table)

2. 虛方法表

對于Super和Sub類,虛方法表大緻如下:(靈魂畫師)

java怎麼調用派_JVM 方法調用之動态分派(詳解)

上面的靈魂畫作是什麼意思呢?

虛方法表中存放着各個方法的實際入口位址。如果某個方法在子類中沒有被重寫,那子類的虛方法表裡面的位址入口和父類相同簽名的方法的位址入口是一緻的,都指向父類的實作入口。如果子類中重寫了這個方法,子類方法表中的位址将會替換為向子類實作版本的入口位址。

從上圖主要得出幾個資訊:

a. 上圖的大部分方法,子類Super和Sub均沒有重寫,那麼都指向父類Object的類型資料。f()和f(int)方法,父類子類都實作了,那麼兩者就指向不同的實作位址。f(char)隻在子類定義實作,自然指向子類的類型資料。

b. 為了程式實作上的友善,具有相同簽名的方法,在父類,子類的虛方法表中都應當具有一樣的索引序号,這樣當類型變換時,僅需要變更查找的方法表,就可以從不同的虛方法表中按索引轉換出所需要的入口位址。

3. 執行個體分析

以本文開頭的代碼進行分析。通過javap指令檢視main方法的指令。

java怎麼調用派_JVM 方法調用之動态分派(詳解)

其中的invokevirtual指令詳細調用過程是這樣的:

1)指令中的#19指的是App類的常量池中第19個常量表的索引項。這個常量表(CONSTATN_Methodref_info)記錄的是方法f()資訊的符号引用,JVM首先根據這個符号引用找到調用方法f()的類的全限定名com.khlin.Super,這是因為變量object被聲明為Super類型。

2) 在Super類型的方法表中查找方法f(),如果找到,則将方法f()在方法表中的索引項(具體值我不了解,這裡将其記為index) 記錄到App類的常量池中第19個常量表中(常量池解析)。是以,如果Super類型方法表中沒有f(),那麼即使Sub類型的方法表有該方法,也會報編譯失敗。

3)在調用invokevirtual指令前有一個aload_1指令,它會将開始建立中堆中的Sub對象的引用壓入操作數棧。然後invokevirtual指令會根據這個Sub對象的引用首先找到堆中的Sub對象,然後進一步找到Sub對象所屬類型的方法表。

4)這時,通過2)查找的index,可以定位到Sub類型方法表中的f()方法,然後通過直接位址找到該方法位元組碼所在的記憶體空間。這就是父類和子類相同簽名的方法索引序号一緻的用處。

4. 綜合考慮:一個可能想錯的例子

将本文開頭的代碼裡的main方法稍作修改,調用其他的方法。

public static void main(String[] args) {

Super object = new Sub();

char c = 'a';

object.f(c);

}

結果将輸出sub : f(int)

明明Sub方法裡有完全一樣類型的f(char)方法,卻調用的是f(int).

相信通過前面的學習,已經可以明白原因了。

在object.f(c)調用時,虛拟機先到Super類的方法表裡,查找最為合适的方法。

Super類裡沒有剛好參數為char的f(char)方法,按照前面靜态分派和參數類型自動轉換的學習,可以知道,編譯器使用了除了f(char)之外最為合适的方法f(int)。擷取到索引後,通過索引到實際對象的Sub方法表裡找到f(int)方法,最終執行的就是Sub類的f(int)方法。

該方法的位元組碼指令證明了上述的論證。

java怎麼調用派_JVM 方法調用之動态分派(詳解)

以上這篇JVM 方法調用之動态分派(詳解)就是小編分享給大家的全部内容了,希望能給大家一個參考,也希望大家多多支援腳本之家。