写在前面

本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

本专栏目录结构和文献引用请见100个问题搞定Java虚拟机

解答

在这里插入图片描述

invokedynamic 是 Java 7 引入的一条新指令,用以支持动态语言的方法调用。
具体来说,它将调用点(CallSite)抽象成一个 Java 类,并且将原本由 Java 虚拟机控制的方法调用以及方法链接暴露给了应用程序。
在运行过程中,每一条 invokedynamic 指令将捆绑一个调用点,并且会调用该调用点所链接的方法句柄。

补充

方法句柄

方法句柄是 invokedynamic 底层机制的基石。 它是一个强类型的、能够被直接执行的引用。

该引用可以指向常规的静态方法或者实例方法,也可以指向构造器或者字段。

唯一标识

方法句柄的类型(MethodType)是由所指向方法的参数类型以及返回类型组成的。它是用来确认方法句柄是否适配的唯一标识。

当使用方法句柄时,我们其实并不关心方法句柄所指向方法的类名或者方法名。

创建

方法句柄的创建是通过 MethodHandles.Lookup 类来完成的。

它提供了多个 API,既可以使用反射 API 中的 Method 来查找,也可以根据类、方法名以及方法句柄类型来查找。

调用方法句柄,和原本对应的调用指令是一致的。

也就是说,对于原本用 invokevirtual 调用的方法句柄,它也会采用动态绑定;而对于原本用 invokespecial 调用的方法句柄,它会采用静态绑定。

权限检查

方法句柄的权限检查发生在创建过程中,相较于反射调用节省了调用时反复权限检查的开销。

操作

方法句柄还支持增删改参数的操作,这些操作是通过生成另一个充当适配器的方法句柄来实现的。

方法句柄 VS 反射 VS 代理

访问控制方面

  1. 反射需要调用setAccesible(),可能会受到安全管理器的禁止警告;
  2. 代理有些情况下通过内部类实现,但是内部类只能访问受限的函数或字段;
  3. 方法句柄则在上下文中对所有方法都有完整的访问权限,并且不会受到安全管理器的限制,这是方法句柄的优势之一。

执行速度方面

  1. 反射的性能会受到参数方法、类型的自动装箱和拆箱、方法内联的影响,相对来讲反射算是执行较慢的了
  2. 通过代理的方式因调用Java函数实现,速度与其它调用函数的速度是一样的,相对较快;
  3. 方法句柄可能不会有代理方式那样的执行速度快,但同样会受到JVM等不同的配置导致速度不同,但从JVM设计者的角度来说,应该是力求达到像调用函数一样快的速度,目前可能是达不到的。方法句柄的调用和反射调用一样,都是间接调用,同样会面临无法内联的问题。

内存开销方面

代理通常声明多个类,需要占用方法区,而方法句柄并不需要像代理一样有多个类的开销,不需要方法区的开销。

invokedynamic 的实现原理

在第一次执行 invokedynamic 指令时,Java 虚拟机会调用该指令所对应的启动方法(BootStrap Method),来生成调用点,并且将之绑定至该 invokedynamic 指令中。

在之后的运行过程中,Java 虚拟机则会直接调用绑定的调用点所链接的方法句柄。

启动方法

在字节码中,启动方法是用方法句柄来指定的。这个方法句柄指向一个返回类型为调用点的静态方法。

该方法必须接收三个固定的参数

  1. Lookup 类实例
  2. 一个用来指代目标方法名字的字符串
  3. 该调用点能够链接的方法句柄的类型

除了这三个必需参数之外,启动方法还可以接收若干个其他的参数,用来辅助生成调用点,或者定位所要链接的目标方法。

上一篇 下一篇