[Golang实现JVM第五篇]静态方法调用的实现

一直以来又长又臭的调用链简直就是Java语言的标志性特色,方法调用可谓是Java世界里表达一切逻辑的基石。现在我们终于具备了实现它的基础。

JVM中的5条方法调用指令

在JVM中触发方法调用的指令有5条,分别是:

  • invokestatic

调用静态方法

  • invokespecial

调用构造方法

  • invokeinterface

调用接口方法

  • invokevirtual

调用对象方法

  • invokedynamic

jdk1.7中引入,给动态语言预留的调用指令。指令的第一个参数不再是代表方法符号引用的CONSTANT_Methodref_info常量,而是变为JDK 1.7新加入的CONSTANT_InvokeDynamic_info常量

<br />

<br />

invokestatic指令的实现

这里面最简单的就是invokestatic静态方法调用指令了,因为静态方法不需要创建对象,属于类。这条指令后面紧跟着两个字节,表示常量池中方法引用常量的下标:

invokestatic byte1 byte2

这样的话我们就可以从常量池中找到CONSTANT_Methodref_info常量,结构如下:

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

可以看到,里面记录了方法所在的类,和一个NameAndType常量的索引。其中NameAndType的结构如下:

type NameAndTypeConst struct {
    Tag uint8
    NameIndex uint16
    DescIndex uint16
}

其中NameIndex又是一个下标,指向的是常量池中的UTF8属性,表示方法的简单名,例如sayHello

DescIndex也是一个下标,指向的是常量池中的UTF8属性,表示方法描述符,例如(ILjava/lang/String;)Ljava/lang/String;。方法描述符描述了一个方法的参数类型、数量和返回类型,其中括号里的(ILjava/lang/String;)表示的是方法参数,I表示基本类型intLjava/lang/String;表示对象类型。注意基本类型跟其他类型之间是没有任何分隔符的,如果是对象类型,则以大写字母L开头,然后紧跟着类的全名,最后以;结束。括号外的Ljava/lang/String;表示此方法的返回类型也是String,如果没有返回值则表示为V,没有参数的话则表示为一对空括号()。综上所述,描述符(ILjava/lang/String;)Ljava/lang/String;表示第一个参数为int类型,第二个参数为String类型,返回类型为String的方法:

String foo(int, String) {}

有了方法名、方法签名、类名,我们就可以唯一确定一个方法了。在调用之前还要注意参数顺序问题,调用前javac会生成一系列参数压栈的指令,但是我们在取参数出栈的时候,由于栈先进先出的性质,弹出参数的顺序跟实际顺序是相反的,这一点一定要小心。

最后我们再来看一下常量池中的UTF8数据项结构:

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

里面的Bytes就是UTF8字节流了,可以直接转换成string。class里所有对字符串的记录都是通过UTF8属性保存的,想引用这个字符串的话就用UTF8属性在常量池的下标来引用。

<br />

<br />

有了上面的分析,我们就可以按图索骥的去实现了。首先,我们要把指令后面的byte1 byte2组装回整数:

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()方法会首先从已经加载的类中查找,如果没有,就会遍历classpath下的所有.class文件(包括jar包中的文件),找到全限定性名相符的class文件,执行class解析逻辑(第二篇中有讲),然后返回一个DefClass结构的指针。

然后就可以直接调用方法了:

// 调用
    return i.ExecuteWithFrame(targetDef, methodName, descriptor, frame)

ExecuteWithFrame方法中实现了方法描述符的解析、方法栈帧的创建和参数的传递,代码比较复杂不再罗列了,可以在这里找到完整代码:https://github.com/wanghongfei/mini-jvm/blob/master/vm/interpreted_execution_engine.go

<br />

<br />

至此我们就完成了JVM中最简单的方法调用指令的实现。为什么说最简单呢,因为invokestatic不需要创建对象,也不涉及复杂的方法查找逻辑,只需要匹配类名、方法名、方法签名就可以了。静态方法调用的实现是以后实现native方法的基石,在下一篇中就会介绍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342