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

一直以來又長又臭的調(diào)用鏈簡直就是Java語言的標(biāo)志性特色,方法調(diào)用可謂是Java世界里表達(dá)一切邏輯的基石?,F(xiàn)在我們終于具備了實(shí)現(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)用對(duì)象方法

  • invokedynamic

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

<br />

<br />

invokestatic指令的實(shí)現(xiàn)

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

invokestatic byte1 byte2

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

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

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

type NameAndTypeConst struct {
    Tag uint8
    NameIndex uint16
    DescIndex uint16
}

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

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

String foo(int, String) {}

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

最后我們?cè)賮砜匆幌鲁A砍刂械?code>UTF8數(shù)據(jù)項(xiàng)結(jié)構(gòu):

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

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

<br />

<br />

有了上面的分析,我們就可以按圖索驥的去實(shí)現(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)
    // 取出目標(biāo)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()方法會(huì)首先從已經(jīng)加載的類中查找,如果沒有,就會(huì)遍歷classpath下的所有.class文件(包括jar包中的文件),找到全限定性名相符的class文件,執(zhí)行class解析邏輯(第二篇中有講),然后返回一個(gè)DefClass結(jié)構(gòu)的指針。

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

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

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

<br />

<br />

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

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

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