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é)碼反匯編結果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)類型的繼承層次關系节预。對于一個確定的方法接收者及方法名的方法調(diào)用叶摄,JAVA編譯器會根據(jù)入?yún)磉x取一個重載方法,這過程有三個階段:
(1)不考慮基本類型的裝箱/拆箱安拟、以及可變長參數(shù)這兩種情況准谚,選取重載方法;
(2)如果在(1)找不到適配的方法去扣,那么采用策略選取重載方法:允許裝箱/拆箱,但排除可變長參數(shù)樊破;
(3)如果在(2)找不到適配的方法愉棱,那么采用裝箱/拆箱與可變長參數(shù)組合使用的策略;
在任何一個階段找到重載方法的話哲戚,解析過程就結束了奔滑。
在JAVA語言規(guī)范里面,上面的過程稱之為“確認潛在的適配方法”(Identify Potentially Applicable Methods)顺少,也就是說朋其,在任一階段可能找到多個候選的方法王浴。
如果找到多個方法的話,再從中選擇最貼切的一個梅猿。這里利用的關鍵信息就是入?yún)㈧o態(tài)類型的繼承層次關系氓辣,原則就是優(yōu)先選擇繼承路徑最短的。舉個簡單的例子袱蚓,現(xiàn)在有兩個候選方法:m1(TypeA a)钞啸、m1(TypeB b),而入?yún)⒌撵o態(tài)類型是TypeC喇潘,三種類型的繼承層次關系是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)用實例構造器<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)用。
其中姊扔,前兩個指令對應的方法惠奸,因為潛在的方法版本唯一,直接引用是具體方法的指針恰梢,在執(zhí)行指令可以直接訪問了佛南,這些方法也稱之為非虛方法梗掰。相反,第3嗅回、4個指令及穗,虛方法及接口方法,得到的直接引用只是方法表的一個索引值妈拌,執(zhí)行指令時還需要進一步解析拥坛,這里有兩個階段。
(1)非接口方法解析(類加載-解析階段)
這里說下非接口方法符號引用解析為直接引用的過程尘分。假設目標符號引用為類C猜惋,則JVM的步驟如下:

  1. 在C中查找符合名字及描述符的方法,如果有則返回方法直接引用(方法指針)培愁,結束著摔;
  2. 如果1沒有找到,按繼承層次從低往高定续,先從C的父類中繼續(xù)搜索谍咆,直至Object類,如果有則返回方法直接引用(方法表索引值)私股,結束摹察;
  3. 如果2沒有找到,在C實現(xiàn)的接口列表及他們父接口中搜索倡鲸,如果找到供嚎,還要滿足一條限制【若C不是抽象類,結果必須不能為抽象方法峭状,不然報錯AbstractMethodError】才能返回克滴。
  4. 如果最后都沒找到,報錯NoSuchMethodError优床。
    當然劝赔,過程找到的方法必須具備訪問權限了,不然報錯IllegalAccessError胆敞。接口方法解析也是類似的原理着帽。

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

  1. 確定操作數(shù)棧頂?shù)谝粋€元素的所指向的對象實際類型,設為C移层;
  2. 在C中查找符合名字及描述符的方法仍翰,如果有則返回方法直接引用(方法指針),結束幽钢;
  3. 如果1沒有找到,按繼承層次從低往高傅是,先從C的父類中繼續(xù)搜索匪燕,直至Object類蕾羊,如果有則返回方法直接引用(方法指針),結束帽驯;
    這個過程其實跟類加載的方法解析是類似的龟再,都是在類繼承層次上按圖索驥∧岜洌可以看到利凑,這里涉及逐層的搜索,性能是堪憂的嫌术,因此在虛擬機的實際實現(xiàn)中會采用空間換時間的策略哀澈,構建虛方法表(對于invokeinterface,則是接口方法表)度气,使用虛方法表索引來替代層層搜索割按。
4. 方法表

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

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

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

參考文獻

  1. Compile-Time Step 2: Determine Method Signature
  2. 虛方法表結構源碼
  3. 深入理解Java虛擬機(第2版)
  4. 極客時間付費課程-深入拆解 Java 虛擬機
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市寥袭,隨后出現(xiàn)的幾起案子路捧,更是在濱河造成了極大的恐慌关霸,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杰扫,死亡現(xiàn)場離奇詭異队寇,居然都是意外死亡,警方通過查閱死者的電腦和手機章姓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門佳遣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凡伊,你說我怎么就攤上這事零渐。” “怎么了窗声?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵相恃,是天一觀的道長。 經(jīng)常有香客問我笨觅,道長拦耐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任见剩,我火速辦了婚禮杀糯,結果婚禮上,老公的妹妹穿的比我還像新娘苍苞。我一直安慰自己固翰,他們只是感情好,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布羹呵。 她就那樣靜靜地躺著骂际,像睡著了一般。 火紅的嫁衣襯著肌膚如雪冈欢。 梳的紋絲不亂的頭發(fā)上歉铝,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機與錄音凑耻,去河邊找鬼太示。 笑死,一個胖子當著我的面吹牛香浩,可吹牛的內(nèi)容都是我干的类缤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邻吭,長吁一口氣:“原來是場噩夢啊……” “哼餐弱!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤膏蚓,失蹤者是張志新(化名)和其女友劉穎猖败,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體降允,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年艺糜,在試婚紗的時候發(fā)現(xiàn)自己被綠了剧董。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡破停,死狀恐怖翅楼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情真慢,我是刑警寧澤毅臊,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站黑界,受9級特大地震影響管嬉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜朗鸠,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一蚯撩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烛占,春花似錦胎挎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芽卿,卻和暖如春揭芍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹬竖。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工沼沈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人币厕。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓列另,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旦装。 傳聞我的和親對象是個殘疾皇子页衙,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容