[Golang實現(xiàn)JVM第五篇]靜態(tài)方法調(diào)用的實現(xiàn)

一直以來又長又臭的調(diào)用鏈簡直就是Java語言的標志性特色,方法調(diào)用可謂是Java世界里表達一切邏輯的基石?,F(xiàn)在我們終于具備了實現(xiàn)它的基礎(chǔ)。

JVM中的5條方法調(diào)用指令

在JVM中觸發(fā)方法調(diào)用的指令有5條,分別是:

  • invokestatic

調(diào)用靜態(tài)方法

  • invokespecial

調(diào)用構(gòu)造方法

  • invokeinterface

調(diào)用接口方法

  • invokevirtual

調(diào)用對象方法

  • invokedynamic

jdk1.7中引入,給動態(tài)語言預留的調(diào)用指令。指令的第一個參數(shù)不再是代表方法符號引用的CONSTANT_Methodref_info常量,而是變?yōu)镴DK 1.7新加入的CONSTANT_InvokeDynamic_info常量

<br />

<br />

invokestatic指令的實現(xiàn)

這里面最簡單的就是invokestatic靜態(tài)方法調(diào)用指令了,因為靜態(tài)方法不需要創(chuàng)建對象,屬于類。這條指令后面緊跟著兩個字節(jié),表示常量池中方法引用常量的下標:

invokestatic byte1 byte2

這樣的話我們就可以從常量池中找到CONSTANT_Methodref_info常量,結(jié)構(gòu)如下:

// 方法引用常量
type MethodRefConstInfo struct {
    Tag uint8
    ClassIndex uint16
    NameAndTypeIndex uint16
}

可以看到,里面記錄了方法所在的類,和一個NameAndType常量的索引。其中NameAndType的結(jié)構(gòu)如下:

type NameAndTypeConst struct {
    Tag uint8
    NameIndex uint16
    DescIndex uint16
}

其中NameIndex又是一個下標,指向的是常量池中的UTF8屬性,表示方法的簡單名,例如sayHello;

DescIndex也是一個下標,指向的是常量池中的UTF8屬性,表示方法描述符,例如(ILjava/lang/String;)Ljava/lang/String;。方法描述符描述了一個方法的參數(shù)類型、數(shù)量和返回類型,其中括號里的(ILjava/lang/String;)表示的是方法參數(shù),I表示基本類型intLjava/lang/String;表示對象類型。注意基本類型跟其他類型之間是沒有任何分隔符的,如果是對象類型,則以大寫字母L開頭,然后緊跟著類的全名,最后以;結(jié)束。括號外的Ljava/lang/String;表示此方法的返回類型也是String,如果沒有返回值則表示為V,沒有參數(shù)的話則表示為一對空括號()。綜上所述,描述符(ILjava/lang/String;)Ljava/lang/String;表示第一個參數(shù)為int類型,第二個參數(shù)為String類型,返回類型為String的方法:

String foo(int, String) {}

有了方法名、方法簽名、類名,我們就可以唯一確定一個方法了。在調(diào)用之前還要注意參數(shù)順序問題,調(diào)用前javac會生成一系列參數(shù)壓棧的指令,但是我們在取參數(shù)出棧的時候,由于棧先進先出的性質(zhì),彈出參數(shù)的順序跟實際順序是相反的,這一點一定要小心。

最后我們再來看一下常量池中的UTF8數(shù)據(jù)項結(jié)構(gòu):

type Utf8InfoConst struct {
    Tag uint8
    Length uint16
    Bytes []byte
}

里面的Bytes就是UTF8字節(jié)流了,可以直接轉(zhuǎn)換成string。class里所有對字符串的記錄都是通過UTF8屬性保存的,想引用這個字符串的話就用UTF8屬性在常量池的下標來引用。

<br />

<br />

有了上面的分析,我們就可以按圖索驥的去實現(xiàn)了。首先,我們要把指令后面的byte1 byte2組裝回整數(shù):

methodRefCpIndex := (indexbyte1 << 8) | indexbyte2

然后取出方法引用常量、取出方法名、方法描述符、方法所在的class全名:

/ 取出引用的方法
    methodRef := def.ConstPool[methodRefCpIndex].(*class.MethodRefConstInfo)
    // 取出方法名
    nameAndType := def.ConstPool[methodRef.NameAndTypeIndex].(*class.NameAndTypeConst)
    methodName := def.ConstPool[nameAndType.NameIndex].(*class.Utf8InfoConst).String()
    // 描述符
    descriptor := def.ConstPool[nameAndType.DescIndex].(*class.Utf8InfoConst).String()
    // 取出方法所在的class
    classRef := def.ConstPool[methodRef.ClassIndex].(*class.ClassInfoConstInfo)
    // 取出目標class全名
    targetClassFullName := def.ConstPool[classRef.FullClassNameIndex].(*class.Utf8InfoConst).String()

加載class:

// 加載
    targetDef, err := i.miniJvm.findDefClass(targetClassFullName)
    if nil != err {
        return fmt.Errorf("failed to load class for '%s': %w", targetClassFullName, err)
    }

這里的findDefClass()方法會首先從已經(jīng)加載的類中查找,如果沒有,就會遍歷classpath下的所有.class文件(包括jar包中的文件),找到全限定性名相符的class文件,執(zhí)行class解析邏輯(第二篇中有講),然后返回一個DefClass結(jié)構(gòu)的指針。

然后就可以直接調(diào)用方法了:

// 調(diào)用
    return i.ExecuteWithFrame(targetDef, methodName, descriptor, frame)

ExecuteWithFrame方法中實現(xiàn)了方法描述符的解析、方法棧幀的創(chuàng)建和參數(shù)的傳遞,代碼比較復雜不再羅列了,可以在這里找到完整代碼:https://github.com/wanghongfei/mini-jvm/blob/master/vm/interpreted_execution_engine.go

<br />

<br />

至此我們就完成了JVM中最簡單的方法調(diào)用指令的實現(xiàn)。為什么說最簡單呢,因為invokestatic不需要創(chuàng)建對象,也不涉及復雜的方法查找邏輯,只需要匹配類名、方法名、方法簽名就可以了。靜態(tài)方法調(diào)用的實現(xiàn)是以后實現(xiàn)native方法的基石,在下一篇中就會介紹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容