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 支持。
使用基本思路
- 创建
MethodType
对象,指定方法的签名 - 在
MethodHandles.Lookup
中查找类型为MethodType
的MethodHandle
- 传入方法参数并调用
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
的静态方法:
-
genericMethodType
: 所有类型都是 Object 类型 -
fromMethodDescriptorString
: 有两个参数,一个是方法描述符,该描述符是基于 字节码 层面的描述符。之后就是一个类加载器。
当然,创建完之后还可以修改返回值类型和参数类型如: changeParameterType
、 changeReturnType
等。
MethodHandles.Lookup
一个工厂类,用于创建 MethodHandle
类对象。它可通过普通的 findXxx() 方法得到相应的 MethodHandle
实例,也可以通过 反射类 来创建实例。如 lookup.unreflect(Method)
等方法。
MethodHandle
MethodHandle
可看成是方法引用,它使得 Java 拥有了类似函数指针或委托的方法别名工具。
注意几点:
- 引用的方法必须和
MethodHandle
的 type 保持一致。 - 这里提到的 type 包括 返回值 和 参数列表 。
-
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));
}
三种调用方式:
-
invokeExact
: 调用此方法与直接调用底层方法一样,需要做到参数类型精确匹配 -
invoke
: 参数类型松散匹配,通过asType自动适配 -
invokeWithArguments
: 直接通过方法参数来调用
CallSite
当 JVM 执行 invokedynamic
指令时,首先需要链接其对应的 动态调用点 。在链接的时候,JVM会先调用一个启动方法(bootstrap method
)。这个启动方法的返回值是 java.lang.invoke.CallSite
类的对象。
在通过启动方法得到了 CallSite 之后,通过这个 CallSite 对象的 getTarget()
可以获取到实际要调用的目标方法句柄。
有了方法句柄之后,对这个 动态调用点 的调用,实际上是代理给方法句柄来完成的。
也就是说,对 invokedynamic
指令的调用实际上就等价于对方法句柄的调用,具体来说是被转换成对方法句柄的invoke方法的调用。
JDK7 中提供了三种类型的动态调用点CallSite的实现:java.lang.invoke.ConstantCallSite
、java.lang.invoke.MutableCallSite
和 java.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 区别
- MethodHandle 在模拟 字节码 层次的方法调用,因而可适用于所有 JVM 语言 ;Reflection 在模拟 Java 层次的方法调用,仅可适用于 Java。
- MethodHandle 可进行 JVM 的内联优化,Reflection 屏蔽了 JVM ,所以完全没有相应的优化。
- MethodHandle 从 JVM 层次支持调用,只需要包含方法必要的信息,所以说是轻量级的,而 Reflection 是 Java Api 层次的反射调用,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含有执行权限等的运行期信息,所以说是重量级的。
- MethodHandle 方法调用需要考虑到
字节码
,而 Reflection 则不用考虑这些。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。