1. 程式人生 > >java方法的虛分派和方法表

java方法的虛分派和方法表

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訪問明顯慢很多。