JVM 方法調(diào)用之動態(tài)分派(詳解)
1. 動態(tài)分派
一個體現(xiàn)是重寫(override)。下面的代碼,運行結(jié)果很明顯。
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();
那么虛擬機是怎么做到動態(tài)分派的呢?
不同的虛擬機有不同的實現(xiàn),最常用的是使用虛方法表(Virtual Method Table)
2. 虛方法表
對于Super和Sub類,虛方法表大致如下:(靈魂畫師)

上面的靈魂畫作是什么意思呢?
虛方法表中存放著各個方法的實際入口地址。如果某個方法在子類中沒有被重寫,那子類的虛方法表里面的地址入口和父類相同簽名的方法的地址入口是一致的,都指向父類的實現(xiàn)入口。如果子類中重寫了這個方法,子類方法表中的地址將會替換為向子類實現(xiàn)版本的入口地址。
從上圖主要得出幾個信息:
a. 上圖的大部分方法,子類Super和Sub均沒有重寫,那么都指向父類Object的類型數(shù)據(jù)。f()和f(int)方法,父類子類都實現(xiàn)了,那么兩者就指向不同的實現(xiàn)地址。f(char)只在子類定義實現(xiàn),自然指向子類的類型數(shù)據(jù)。
b. 為了程序?qū)崿F(xiàn)上的方便,具有相同簽名的方法,在父類,子類的虛方法表中都應(yīng)當(dāng)具有一樣的索引序號,這樣當(dāng)類型變換時,僅需要變更查找的方法表,就可以從不同的虛方法表中按索引轉(zhuǎn)換出所需要的入口地址。
3. 實例分析
以本文開頭的代碼進(jìn)行分析。通過javap命令查看main方法的指令。

其中的invokevirtual指令詳細(xì)調(diào)用過程是這樣的:
1)指令中的#19指的是App類的常量池中第19個常量表的索引項。這個常量表(CONSTATN_Methodref_info)記錄的是方法f()信息的符號引用,JVM首先根據(jù)這個符號引用找到調(diào)用方法f()的類的全限定名com.khlin.Super,這是因為變量object被聲明為Super類型。
2) 在Super類型的方法表中查找方法f(),如果找到,則將方法f()在方法表中的索引項(具體值我不了解,這里將其記為index) 記錄到App類的常量池中第19個常量表中(常量池解析)。因此,如果Super類型方法表中沒有f(),那么即使Sub類型的方法表有該方法,也會報編譯失敗。
3)在調(diào)用invokevirtual指令前有一個aload_1指令,它會將開始創(chuàng)建中堆中的Sub對象的引用壓入操作數(shù)棧。然后invokevirtual指令會根據(jù)這個Sub對象的引用首先找到堆中的Sub對象,然后進(jìn)一步找到Sub對象所屬類型的方法表。
4)這時,通過2)查找的index,可以定位到Sub類型方法表中的f()方法,然后通過直接地址找到該方法字節(jié)碼所在的內(nèi)存空間。這就是父類和子類相同簽名的方法索引序號一致的用處。
4. 綜合考慮:一個可能想錯的例子
將本文開頭的代碼里的main方法稍作修改,調(diào)用其他的方法。
public static void main(String[] args) {
Super object = new Sub();
char c = 'a';
object.f(c);
}
結(jié)果將輸出sub : f(int)
明明Sub方法里有完全一樣類型的f(char)方法,卻調(diào)用的是f(int).
相信通過前面的學(xué)習(xí),已經(jīng)可以明白原因了。
在object.f(c)調(diào)用時,虛擬機先到Super類的方法表里,查找最為合適的方法。
Super類里沒有剛好參數(shù)為char的f(char)方法,按照前面靜態(tài)分派和參數(shù)類型自動轉(zhuǎn)換的學(xué)習(xí),可以知道,編譯器使用了除了f(char)之外最為合適的方法f(int)。獲取到索引后,通過索引到實際對象的Sub方法表里找到f(int)方法,最終執(zhí)行的就是Sub類的f(int)方法。
該方法的字節(jié)碼指令證明了上述的論證。

以上這篇JVM 方法調(diào)用之動態(tài)分派(詳解)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring中的BeanFactory與FactoryBean的講解
今天小編就為大家分享一篇關(guān)于spring中的BeanFactory與FactoryBean的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01
Spring Boot中的JdbcTemplate是什么及用法小結(jié)
Spring Boot中的JdbcTemplate是一個強大的數(shù)據(jù)庫訪問工具,它簡化了數(shù)據(jù)庫操作的過程,在本文中,我們了解了JdbcTemplate的基本概念,并演示了如何在Spring Boot應(yīng)用程序中使用它,感興趣的朋友跟隨小編一起看看吧2023-10-10
java實現(xiàn)定制數(shù)據(jù)透視表的示例詳解
數(shù)據(jù)透視表(Pivot?Table)是一種數(shù)據(jù)分析工具,通常用于對大量數(shù)據(jù)進(jìn)行匯總、分析和展示,本文主要介紹了如何使用Java將計算項添加到數(shù)據(jù)透視表中,感興趣的可以了解下2023-12-12
關(guān)于Java中Comparable 和 Comparator的用法
這篇文章主要介紹了關(guān)于Java中Comparable 和 Comparator的用法,Comparable 和 Comparator 是關(guān)于排序的兩個接口,用來實現(xiàn) Java 集合中的的排序功能,需要的朋友可以參考下2023-04-04
SpringCloud通過MDC實現(xiàn)分布式鏈路追蹤
在DDD領(lǐng)域驅(qū)動設(shè)計中,我們使用SpringCloud來去實現(xiàn),但排查錯誤的時候,通常會想到Skywalking,但是引入一個新的服務(wù),增加了系統(tǒng)消耗和管理學(xué)習(xí)成本,對于大型項目比較適合,但是小的項目顯得太過臃腫了,所以本文介紹了SpringCloud通過MDC實現(xiàn)分布式鏈路追蹤2024-11-11
詳解Java CompletableFuture使用方法以及與FutureTask的區(qū)別
CompletableFuture實現(xiàn)了CompletionStage接口和Future接口,前者是對后者的一個擴展,增加了異步回調(diào)、流式處理、多個Future組合處理的能力,使Java在處理多任務(wù)的協(xié)同工作時更加順暢便利2021-10-10

