JVM中的invokedynamic

浅谈一下自JVM发行以来首次引入的新指令。

为了支持更多语言

官方在论文中阐述新指令的引入动机是为了更好的支持更多的语言,因为当时已经不再是Java这一种语言在使用JVM了。首要解决的是诸如Python、Ruby这类动态类型语言的支持问题。Java是强类型的语言,尽管在编译后真正跑在虚拟机上是去类型的,可是在最终invoke时要做类型检查,如果这时候签名不一致回报错,所以编译时就要确定这些参数。可是动态类型的语言只有在运行时才能知道具体类型,因此在没有invokedynamic前,Jython和JRuby实现起来很繁琐。

这个invoke类型的引入也同时引入了Bootstrap Method(简称BSM)的概念和一个新的对象CallSite。BSM构造CallSite,CallSite存储MethodHandle。

BSM

一个Bootstrap Method通常存储在常量池中,一般认为BSM都是static的。它的前三个参数和返回类型均有约定,是用户定义的。

1
static CallSite bootstrap(Lookup caller, String name, MethodType type)

参数解释:

  • caller: 用以查找MethodHandle的方法
  • name: 被查找方法的名字
  • type: 方法的签名

可以拥有251个额外的静态参数,因为JVM启动的时候就会load这个BSM,所以要求静态。

CallSite

里面存储的MethodHandle,可以在BSM构造后使用setTarget()重新设定一个MH,但是签名必须一致。

执行过程

invokedynamic先调用BSM构造出CallSite,这一步叫做调用点解析。然后调用解析好的调用点。Dalvik中将invokedynamic拆成invoke-custom和invoke-polymorphic,前者可以认为是弱化的dynamic,而后者则是一个Dalvik中支持签名多态的新指令,由Methodhandle.invoke和MethodHandle.invokeExact实现。

Java 8 中的匿名函数

官方在设计文档中指出,虽然lambda expression可以用inner class,invokedynamic,MethodHandle Proxies等多种方法实现,但是因为一些历史原因最终选择了invokedynamic,这就使得这一条指令肩负着动态类型语言和函数式特性两大任务。

本质上lambda expression在Java中是一个Desugar的过程,最终推到runtime去做bind。下面是J8中LambdaMetafactory的一个BSM:

1
2
3
4
5
6
static CallSite	metafactory​(MethodHandles.Lookup caller, 
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)

可以尝试写几个lambda expression后,通过javap反汇编看到编译后对应的invoke指令。