java:方法的虛分派(virtual dispatch)和方法表(method table)
Java方法調用的虛分派
虛分配(Virtual Dispatch)
首先從字節碼中對方法的調用說起。Java的bytecode中方法的調用實現分為四種指令:
invokevirtual為最常見的情況,包含virtual dispatch機制;
invokerspecial是作為對private和構造方法的調用,繞過了virtual dispatch;
invokeinterface的實現跟invokevirtual類似;
invokestatic是對靜態方法的調用;
其中最復雜的要屬invokevirtual指令,它涉及到了多態的特性,使用virtual dispatch做方法調用。
virtual dispatch機制會首先從receiver(被調用方法的對象)的類的實現中查找對應的方法,如果沒找到,則去父類查找,直找到函數並實現調用,而不是依賴於引用的類型。
下面是一段有趣的代碼。反映了virtual dispatch機制和一般的field訪問的不同。

運行結果:

前兩行輸出中,對於Intro這個屬性的訪問,直接指向了父類中的變量,因為引用類型為父類。
第二行對於target()的方法調用,則是指向了子類中的方法,雖然引用類型也為父類,但這是虛分配的結果,虛分配不管引用類型的,只查被調用對象的類型·。
既然需分派機制是從被調用對象本身的類開始查找,那么對於一個覆蓋了父類中某方法的子類的對象,是無法調用父類中那個被覆蓋的方法的嗎?
在虛分派機制中這確實是不可以的。但卻可以通過invokespecial實現。如下代碼:

func()就成功地調用了父類的方法target(),雖然target()已經被子類重寫了。具體的調用細節,從字節碼中可以看到:

其中使用了invokespecial指令,而不是施行需分派策略的invokevirtual指令。
方法表(Method Table)
介紹了虛分派,接下來介紹是它的一種實現方法—方法表。類似於C++虛函數表vtbl。
在有的JVM實現中,使用了方法表機制實現虛分派,而有時候,為了節省內存可能不采用方法表的實現。
不要被方法表這個名字迷惑,它並不是記錄所有方法的表。它是為虛分派服務,不會記錄用invokestatic調用的靜態方法和用invokespecial調用的夠着函數和私有方法。
JVM會在鏈接類的過程中,給類分配相應的方法表內存空間。每個類對應一個方法表。這些都是存在於method area區中的。這里與C++略有不同,C++中每個對象的第一個指針就是指向了相應的虛函數表。而Java中每個對象索引到對應的類,在對應的類數據中對應一個方法表。
一種方法表的實現如下:
父類的方法比子類的方法先得到解析,即父類的方法相對於子類的方法位於表的前列。
表中每項對應於一個方法,索引到實際方法的實際代碼上。如果子類重寫了父類中某個方法的代碼,則該方法第一次出現的位置的索引更換到子類的實現代碼上,而不會在方法表中出現新的項。
JVM運行時,當代碼索引到一個方法時,是根據它在方法表中的偏移量來實現訪問。(第一次執行到調用指令時,會執行解釋,將符號索引替換為對應的直接索引)。
invokeinterface與invokevirtual的比較
當使用invokeinterface來調用方法時,由於不同的類可以實現同一interface,我們無法確定在某個類中的interface中的方法處在哪個位置。於是,也就無法解釋CONSTANT_interfaceMethodref-info為直接索引,而必須每次都執行一次在methodtable中的搜索了。所以,在這種實現中,通過invokeinterface訪問方法比通過invokevirtual訪問明顯慢很多。
