1

JSR 292 学习

Add a new bytecode, invokedynamic ,
that supports efficient and flexible execution of method invocations in the absence of static type information.
添加一个字节码 invokedynamic 。用于提供在缺乏静态类型信息时高效和灵活的执行方法调用。
invokedynamic 是针对 JVM 的,用于更好的支持动态 JVM 语言的函数调用; JSR292 实现则提供在 Java 语言层面上的 invokedynamic 调用。

JSR292 实现于 JDK7,以下为实现方案的类。

└─java
    └─lang
        ├─invoke
        |   ├─CallSite
        |   ├─ConstantCallSite
        |   ├─MethodHandle
        |   ├─MethodHandleProxies
        |   ├─MethodHandles
        |   ├─MethodHandles.Lookup
        |   ├─MethodType
        |   ├─MutableCallSite
        |   ├─SwitchPoint
        |   └─VolatileCallSite
        ├─ClassValue
        └─BootstrapMethodError

API 使用

JSR292 API 提供了 invokedynamic 字节码的 API 支持。

使用基本思路

  1. 创建 MethodType 对象,指定方法的签名
  2. MethodHandles.Lookup 中查找类型为 MethodTypeMethodHandle
  3. 传入方法参数并调用 MethodHandle.invoke 或者 MethodHandle.invokeExact 方法

MethodType

用于指定方法的类型,此处的方法类型与 Java 的方法判断有点区别,Java 的方法签名不包括 返回值 ,但是 MethodType 却是通过 返回值 和 参数类型 来确定。并且该类型不包含 方法名

// 快速创建一个 MethodType 。
MethodType methodType = MethodType.methodType(String.class, Object.class);

// 也可以通过 MethodHandle 实例获取它要调用的方法实例
// MethodHandle handle = ...
MethodType type = handle.type();

除了这两种获取 MethodType 的方法(实际上第二种不算,MethodHandle 对象的创建依赖于 MethodType)。下面还有两种,它也是 MethodType 的静态方法:

  1. genericMethodType: 所有类型都是 Object 类型
  2. fromMethodDescriptorString: 有两个参数,一个是方法描述符,该描述符是基于 字节码 层面的描述符。之后就是一个类加载器。

当然,创建完之后还可以修改返回值类型和参数类型如: changeParameterTypechangeReturnType 等。

MethodHandles.Lookup

一个工厂类,用于创建 MethodHandle 类对象。它可通过普通的 findXxx() 方法得到相应的 MethodHandle 实例,也可以通过 反射类 来创建实例。如 lookup.unreflect(Method) 等方法。

MethodHandle

MethodHandle 可看成是方法引用,它使得 Java 拥有了类似函数指针或委托的方法别名工具。

注意几点:

  1. 引用的方法必须和 MethodHandle 的 type 保持一致。
  2. 这里提到的 type 包括 返回值参数列表
  3. MethodHandle 也是可执行及可以进行转换。

几个 MethodHandle 方法与字节码的对应:

MethodHandle方法 字节码 描述
findStatic invokestatic 调用静态方法
findSpecial invokespecial 调用实例构造方法,私有方法,父类方法。
findVirtual invokevirtual 调用所有的虚方法
findVirtual invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象。

示例

public void example() throws Throwable {
    // MethodHandles.Lookup 类似于 MethodHandle 工厂类,用于创建 MethodHandle
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh = lookup.findStatic(String.class, "valueOf", MethodType.methodType(String.class, int.class));

    String result = (String) mh.invoke(1);
    System.out.println("1".equals(result));
}

三种调用方式:

  1. invokeExact: 调用此方法与直接调用底层方法一样,需要做到参数类型精确匹配
  2. invoke: 参数类型松散匹配,通过asType自动适配
  3. invokeWithArguments: 直接通过方法参数来调用

CallSite

当 JVM 执行 invokedynamic 指令时,首先需要链接其对应的 动态调用点 。在链接的时候,JVM会先调用一个启动方法(bootstrap method)。这个启动方法的返回值是 java.lang.invoke.CallSite 类的对象。

在通过启动方法得到了 CallSite 之后,通过这个 CallSite 对象的 getTarget() 可以获取到实际要调用的目标方法句柄。
有了方法句柄之后,对这个 动态调用点 的调用,实际上是代理给方法句柄来完成的。
也就是说,对 invokedynamic 指令的调用实际上就等价于对方法句柄的调用,具体来说是被转换成对方法句柄的invoke方法的调用。

JDK7 中提供了三种类型的动态调用点CallSite的实现:java.lang.invoke.ConstantCallSitejava.lang.invoke.MutableCallSitejava.lang.invoke.VolatileCallSite

ConstantCallSite

表示的调用点绑定的是一个固定的方法句柄,一旦链接之后,就无法修改。示例如下:

public void constantCallSite() throws Throwable {
    MethodType type = MethodType.methodType(String.class, int.class, int.class);

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle handle = lookup.findVirtual(String.class, "substring", type);

    ConstantCallSite callSite = new ConstantCallSite(handle);
    MethodHandle invoker = callSite.dynamicInvoker();
    String result = (String) invoker.invoke("Hello", 2, 3);
    System.out.println(result);
}
MutableCallSite

表示的调用点则允许在运行时动态修改其目标方法句柄,即可以重新链接到新的方法句柄上。示例如下:

/**
 * MutableCallSite 允许对其所关联的目标方法句柄通过setTarget方法来进行修改。
 * 以下为 创建一个 MutableCallSite,指定了方法句柄的类型,则设置的其他方法也必须是这种类型。
 */
public void useMutableCallSite() throws Throwable {
    MethodType type = MethodType.methodType(int.class, int.class, int.class);
    MutableCallSite callSite = new MutableCallSite(type);
    MethodHandle invoker = callSite.dynamicInvoker();

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle maxHandle = lookup.findStatic(Math.class, "max", type);
    callSite.setTarget(maxHandle);
    int result = (int) invoker.invoke(3, 5);
    System.out.println(result == 5);

    MethodHandle minHandle = lookup.findStatic(Math.class, "min", type);
    callSite.setTarget(minHandle);
    result = (int) invoker.invoke(3, 5);
    System.out.println(result == 3);
}

MutableCallSite.syncAll() 提供了方法来强制要求各个线程中 MutableCallSite 的使用者立即获取最新的目标方法句柄。
但这个时候也可以选择使用 VolatileCallSite

VolatileCallSite

作用与 MutableCallSite 类似,不同的是它适用于多线程情况,用来保证对于目标方法句柄所做的修改能够被其他线程看到。
此处便不再提供示例,可参考 MutableCallSite

MethodHandle 与 Method 区别

  1. MethodHandle 在模拟 字节码 层次的方法调用,因而可适用于所有 JVM 语言 ;Reflection 在模拟 Java 层次的方法调用,仅可适用于 Java。
  2. MethodHandle 可进行 JVM 的内联优化,Reflection 屏蔽了 JVM ,所以完全没有相应的优化。
  3. MethodHandle 从 JVM 层次支持调用,只需要包含方法必要的信息,所以说是轻量级的,而 Reflection 是 Java Api 层次的反射调用,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含有执行权限等的运行期信息,所以说是重量级的。
  4. MethodHandle 方法调用需要考虑到 字节码,而 Reflection 则不用考虑这些。

参考

  1. JDK1.8下关于MethodHandle问题
  2. Invokedynamic 和 MethodHandle的缘由
  3. MethodHandle与反射Method区别

魏晋秋
47 声望2 粉丝

勿惧勿怕


« 上一篇
gitignore 规则
下一篇 »
初识 JNI