JAVA方法調(diào)用機制

1. 一個方法調(diào)用的例子
public class CYoungMan extends YoungMan{
    @Override
    public String meet(Object obj) {
        return "Object class";
    }
    @Override
    public String meet(String str) {
        return "String class";
    }
    public static void main(String[] args) {
        Object str = "kat";
        YoungMan ym = new CYoungMan();
        String result = ym.meet(str);
    }
}

在上面的例子中,對于方法調(diào)用ym.meet(str),我們大概知道有兩個階段:
(1)在編譯階段,根據(jù)方法接收者ym的靜態(tài)類型(static type)即YoungMan,以及參數(shù)str的靜態(tài)類型Object,來決定該方法調(diào)用的符號引用:YoungMan.meet:(Ljava/lang/Object;)Ljava/lang/String;(見javap的字節(jié)碼反匯編結(jié)果invokevirtual #7);
(2)在運行時階段,根據(jù)方法接收者ym的實際類型(actual type)即CYoungMan以及它的繼承層級,來確定方法實際引用。
上面的兩個階段分別是方法重載解析、動態(tài)分派。

說明:
1、 方法重載解析,國內(nèi)一般翻譯為靜態(tài)分派(相對于動態(tài)分派),但實際英文文檔是 Method Overload Resolution。 這里個人感覺按英文語義翻譯更好,所以采用這個。

  ...
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: ldc           #4                  // String kat
         2: astore_1
         3: new           #5                  // class method/invoke/CYoungMan
         6: dup
         7: invokespecial #6                  // Method "<init>":()V
        10: astore_2
        11: aload_2
        12: aload_1
        13: invokevirtual #7                  // Method method/invoke/YoungMan.meet:(Ljava/lang/Object;)Ljava/lang/String;
        16: astore_3
        17: return
  ...
2. 方法重載解析[1] (Method Overload Resolution)

可以看到,編譯階段能確定的、能使用的信息只有方法接收者或者入?yún)⒌撵o態(tài)類型,以及入?yún)㈧o態(tài)類型的繼承層次關(guān)系。對于一個確定的方法接收者及方法名的方法調(diào)用,JAVA編譯器會根據(jù)入?yún)磉x取一個重載方法,這過程有三個階段:
(1)不考慮基本類型的裝箱/拆箱、以及可變長參數(shù)這兩種情況,選取重載方法;
(2)如果在(1)找不到適配的方法,那么采用策略選取重載方法:允許裝箱/拆箱,但排除可變長參數(shù);
(3)如果在(2)找不到適配的方法,那么采用裝箱/拆箱與可變長參數(shù)組合使用的策略;
在任何一個階段找到重載方法的話,解析過程就結(jié)束了。
在JAVA語言規(guī)范里面,上面的過程稱之為“確認潛在的適配方法”(Identify Potentially Applicable Methods),也就是說,在任一階段可能找到多個候選的方法。
如果找到多個方法的話,再從中選擇最貼切的一個。這里利用的關(guān)鍵信息就是入?yún)㈧o態(tài)類型的繼承層次關(guān)系,原則就是優(yōu)先選擇繼承路徑最短的。舉個簡單的例子,現(xiàn)在有兩個候選方法:m1(TypeA a)m1(TypeB b),而入?yún)⒌撵o態(tài)類型是TypeC,三種類型的繼承層次關(guān)系是TypeC->TypeB->TypeA,那么方法m1(TypeB b)肯定是最貼切的(最具體的),因為繼承路徑最短。

3. 動態(tài)分派 (Dynamic Dispatch)

對于重載解析中確定的方法,由于重寫(和繼承)特性而可能存在多個潛在的方法版本,必須在運行期按實際類型確定方法版本,即動態(tài)分派。
在運行期執(zhí)行時,JVM提供了有5種方法調(diào)用字節(jié)碼指令:
(1)invokestatic,調(diào)用靜態(tài)方法;
(2)invokespecial,調(diào)用實例構(gòu)造器<init>方法、私有方法和父類方法;
(3)invokevirtual,調(diào)用所有的虛方法;
(4)invokeinterface,調(diào)用接口方法;
(5)invokedynamic,調(diào)用動態(tài)方法;
前四種指令,均在類加載-解析階段將符號引用解析為方法的直接引用。至于最后的invokedynamic,是在JDK7中新增的指令,目的是在JVM中更好地運行動態(tài)類型語言。簡單說,就是在運行期能實現(xiàn)“重載方法解析+動態(tài)分派”兩重功能的方法解析調(diào)用。
其中,前兩個指令對應(yīng)的方法,因為潛在的方法版本唯一,直接引用是具體方法的指針,在執(zhí)行指令可以直接訪問了,這些方法也稱之為非虛方法。相反,第3、4個指令,虛方法及接口方法,得到的直接引用只是方法表的一個索引值,執(zhí)行指令時還需要進一步解析,這里有兩個階段。
(1)非接口方法解析(類加載-解析階段)
這里說下非接口方法符號引用解析為直接引用的過程。假設(shè)目標符號引用為類C,則JVM的步驟如下:

  1. 在C中查找符合名字及描述符的方法,如果有則返回方法直接引用(方法指針),結(jié)束;
  2. 如果1沒有找到,按繼承層次從低往高,先從C的父類中繼續(xù)搜索,直至Object類,如果有則返回方法直接引用(方法表索引值),結(jié)束;
  3. 如果2沒有找到,在C實現(xiàn)的接口列表及他們父接口中搜索,如果找到,還要滿足一條限制【若C不是抽象類,結(jié)果必須不能為抽象方法,不然報錯AbstractMethodError】才能返回。
  4. 如果最后都沒找到,報錯NoSuchMethodError。
    當然,過程找到的方法必須具備訪問權(quán)限了,不然報錯IllegalAccessError。接口方法解析也是類似的原理。

(2)invokevirtual 的解析過程(指令執(zhí)行階段)

  1. 確定操作數(shù)棧頂?shù)谝粋€元素的所指向的對象實際類型,設(shè)為C;
  2. 在C中查找符合名字及描述符的方法,如果有則返回方法直接引用(方法指針),結(jié)束;
  3. 如果1沒有找到,按繼承層次從低往高,先從C的父類中繼續(xù)搜索,直至Object類,如果有則返回方法直接引用(方法指針),結(jié)束;
    這個過程其實跟類加載的方法解析是類似的,都是在類繼承層次上按圖索驥??梢钥吹剑@里涉及逐層的搜索,性能是堪憂的,因此在虛擬機的實際實現(xiàn)中會采用空間換時間的策略,構(gòu)建虛方法表(對于invokeinterface,則是接口方法表),使用虛方法表索引來替代層層搜索。
4. 方法表

在前面提到,方法表是用來加速類加載階段的方法解析、虛方法/接口方法指令的解析過程的,所以方法表的構(gòu)建一定是在類加載過程的解析階段之前。
你大概猜到了,是的,是在準備階段,除了為靜態(tài)字段分配內(nèi)存外,還構(gòu)造了類的方法表。
方法表[2]的構(gòu)建過程就不贅言了,下面列一下它的兩點特征:

  1. 本質(zhì)上是一個數(shù)組,元素指向當前類或者祖先類的方法(方法引用);
  2. 子類方法表包含父類方法表中所有方法,如果某個方法在子類中沒有被重寫,則方法引用與父類的一致,否則用自己實現(xiàn)的方法替換父類的;
    相當于把一個分層結(jié)構(gòu)水平鋪開,無論類加載的方法解析還是虛方法調(diào)用指令的解析,只需要在當前實際類型的方法表中遍歷搜索即可,不需要層層遞歸搜索了。

問題與討論:
1、對包裝類型拆箱,不考慮空指針問題,相當于把問題拋給coder?

參考文獻

  1. Compile-Time Step 2: Determine Method Signature
  2. 虛方法表結(jié)構(gòu)源碼
  3. 深入理解Java虛擬機(第2版)
  4. 極客時間付費課程-深入拆解 Java 虛擬機
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容