方法調(diào)用
java程序語言提供了兩種基本方法:實例方法和類(靜態(tài))方法.其不同點是:
- 實例方法在調(diào)用前需要一個對象實例,而類方法不需要.
- 實例方法使用動態(tài)綁定晴股,而類方法使用靜態(tài)綁定.
JVM用invokevirtual
和invokestatic
兩種不同的指令來分別處理實例方法和類方法的調(diào)用.
動態(tài)鏈接
符號引用 是一簇唯一標識一個方法,包括類名,方法名产场,和方法描述(方法返回值,參數(shù)類型等)等信息的存放在常量池中的標識.當在運行時舞竿,遇到方法調(diào)用的指令時京景,就會解決符號引用.
為了解決符號引用,JVM定位到被符號引用的標識骗奖,并將其替換成直接引用.直接引用确徙,比如一個指針或一個偏移量靡菇,能夠使虛擬機調(diào)用方法更快(沒有使用過).
驗證
在將符號引用轉(zhuǎn)成直接引用時,JVM會附加執(zhí)行一些驗證檢查操作.這些檢查確保java語言規(guī)則被遵守并且調(diào)用指令能被安全執(zhí)行.比如米愿,虛擬機首先確保符號引用的方法存在.如果存在,虛擬機檢查確保當前類能合法進入到要調(diào)用的方法.比如鼻吮,如果方法是private
育苟,它必須是當前類的成員.如果有一個檢查失敗,JVM將拋出異常.
對象引用和參數(shù)問題
在上面準備好了直接引用后椎木,JVM將要準備參數(shù)违柏,實例方法需要有對象引用.這些參數(shù)必須在調(diào)用指令之前必須通過字節(jié)碼指令push到調(diào)用方法(發(fā)起方)的操作數(shù)棧中.
Pushing and poping 棧幀
調(diào)用一個方法時,JVM將為被調(diào)用方法創(chuàng)建一個新的棧幀.棧幀包括方法的本地變量表香椎、操作數(shù)棧和該虛擬機特殊實現(xiàn)需要的其他信息.本地變量表和操作數(shù)棧的大小在編譯時就已經(jīng)計算好了.
當方法調(diào)用時將在棧中增加一個新的棧幀就叫做Pushing
一個棧幀.當方法返回時移除一個棧幀叫做poping
一個棧幀.
調(diào)用java方法
當調(diào)用一個java方法時漱竖,JVM將push一個新的棧幀到當前的java棧中.
針對一個實例方法,VM 將pops調(diào)用方法棧幀中的對象引用和操作數(shù)棧中的參數(shù).JVM創(chuàng)建一個新的棧幀畜伐,并且將新的棧幀中本地變量的0(this)替換為對象引用馍惹,其他的1、2替換為其他參數(shù).
針對類方法玛界,VM僅僅調(diào)用方法棧幀中的操作數(shù)棧中的參數(shù)万矾,并用他們替換新的棧幀中的本地變量0,1,2....
一旦新的棧幀中的本地變量表替換完成,VM將新的棧幀作為當前棧幀并且設(shè)置程序計數(shù)器指向新方法的第一條指令.
The JVM specification does not require a particular implementation for the Java stack. Frames could be allocated individually from a heap, or they could be taken from contiguous memory, or both. If two frames are contiguous, however, the virtual machine can just overlap them such that the top of the operand stack of one frame forms the bottom of the local variables of the next. In this scheme, the virtual machine need not copy objectref and args from one frame to another, because the two frames overlap. The operand stack word containing objectref in the calling method's frame would be the same memory location as local variable 0 of the new frame
調(diào)用本地方法
如果調(diào)用的方法是本地方法,JVM以一種依賴實現(xiàn)的方法來調(diào)用慎框。VM不會為本地方法向java棧push一個新的棧幀良狈,At the point at which the thread enters the native method, it leaves the Java stack behind. When the native method returns, the Java stack once again will be used.
其他方法調(diào)用形式
盡管實例方法一般通過invokevirtual
來調(diào)用,然而invokespecial
和invokeinterface
這兩個操作碼被用來調(diào)用某種情形的特殊方法.
invokespecial
用于基于引用類型的實例化方法.用于三種情形:
- 調(diào)用實力初始化(<init>)方法
- 調(diào)用私有方法
- 調(diào)用使用
super
關(guān)鍵字的方法
invokeinterface
用來調(diào)用一個接口引用的實例方法.
invokespecial
與invokevirtual
不同點:
invokespecial
選擇的方法是基于引用類型而不是對象的類類型.或者說笨枯,是靜態(tài)綁定而不是動態(tài)綁定薪丁。
invokespecial與私有方法
class Superclass {
private void interestingMethod() {
System.out.println("Superclass's interesting method.");
}
void exampleMethod() {
interestingMethod();
}
}
class Subclass extends Superclass {
void interestingMethod() {
System.out.println("Subclass's interesting method.");
}
public static void main(String args[]) {
Subclass me = new Subclass();
me.exampleMethod();
}
}
以上將打印:
Superclass's interesting method.
由于invokespecial
,VM將選擇基于引用類型的方法,所以如此打印.
方法調(diào)用事例
interface inYourFace {
void interfaceMethod ();
}
class itsABirdItsAPlaneItsSuperClass implements inYourFace {
itsABirdItsAPlaneItsSuperClass(int i) {
super(); // invokespecial (of an <init>)
}
static void classMethod() {
}
void instanceMethod() {
}
final void finalInstanceMethod() {
}
public void interfaceMethod() {
}
}
class subClass extends itsABirdItsAPlaneItsSuperClass {
subClass() {
this(0); // invokespecial (of an <init>)
}
subClass(int i) {
super(i); // invokespecial (of an <init>)
}
private void privateMethod() {
}
void instanceMethod() {
}
final void anotherFinalInstanceMethod() {
}
void exampleInstanceMethod() {
instanceMethod(); // invokevirtual
super.instanceMethod(); // invokespecial
privateMethod(); // invokespecial
finalInstanceMethod(); // invokevirtual
anotherFinalInstanceMethod(); // invokevirtual
interfaceMethod(); // invokevirtual
classMethod(); // invokestatic
}
}
class unrelatedClass {
public static void main(String args[]) {
subClass sc = new subClass(); // invokespecial (of an <init>)
subClass.classMethod(); // invokestatic
sc.classMethod(); // invokestatic
sc.instanceMethod(); // invokevirtual
sc.finalInstanceMethod(); // invokevirtual
sc.interfaceMethod(); // invokevirtual
inYourFace iyf = sc;
iyf.interfaceMethod(); // invokeinterface
}
}
方法返回
JVM會使用針對每一種返回類型的操作來返回.返回值將從操作數(shù)棧pop并且push到調(diào)用方法的方法棧幀中.當前的棧幀pop,被調(diào)用方法的棧幀變成當前的.程序計數(shù)器將重置為調(diào)用這個方法的指令的下一條指令.
操作碼描述:
ireturn none pop int, push onto stack of calling method and return
lreturn none pop long, push onto stack of calling method and return
freturn none pop float, push onto stack of calling method and return
dreturn none pop double, push onto stack of calling method and return
areturn none pop object reference, push onto stack of calling method and return
-
return none return void
The ireturn instruction is used for methods that return int, char, byte, or short.
結(jié)論
- 實例方法是動態(tài)綁定的,除了
<init>
馅精、private和super關(guān)鍵字調(diào)用的方法,這三種特殊情況严嗜,實例方法是靜態(tài)綁定的. - 類方法是靜態(tài)綁定的.
- 與接口引用相關(guān)的實例方法可能比同樣的對象關(guān)聯(lián)的方法慢.
參考鏈接
how the java virtual machine handles method invocation and return