调用指令分布
invokeinterface
(Invoke interface method)
用以调用接口方法,在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
invokevirtual:
(Invoke instance method; dispatch based on class)
指令用于调用对象的实例方法,根据对象的实际类型进行分派:(静态分派[重载]、动态分派[重写])
invokestatic
(Invoke a class (static) method )
用以调用类方法、类操作变量的getter/setter方法等
invokespecial
(Invoke instance method)
指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。以及包含一些实例构造器()V 或者类构造器()V
**(Invoke instance method; special handling for superclass, private, and instance initialization method invocations )**
invokedynamic
(Invoke dynamic method)
JDK1.7新加入的一个虚拟机指令,相比于之前的四条指令,他们的分派逻辑都是固化在JVM内部,而invokedynamic则用于处理新的方法分派:它允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,从而达到动态语言的支持。
public class JVMInstructionTest implements Runnable {
public JVMInstructionTest() {
System.out.println("constructor method");
}
private void s() {
System.out.println("private method");
}
static void print() {
System.out.println("static method");
}
void p() {
System.out.println("instance method");
}
public void d(String str) {
System.out.println("for method handle " + str);
}
static void ddd(String str) {
System.out.println("static method for method handle " + str);
}
public static void main(String[] args) throws Throwable {
/**
* invoke special
*/
JVMInstructionTest test = new JVMInstructionTest();
/**
* invoke special
*/
test.s();
/**
* invoke virtual
*/
test.p();
/**
* invoke static
*/
print();
/**
* invoke interface
*/
Runnable r = new JVMInstructionTest();
r.run();
/**
* Java 8中,lambda表达式和默认方法时,底层会生成和使用invoke dynamic
* invoke dynamic
*/
List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.stream().forEach(System.out::println);
}
@Override
public void run() {
System.out.println("interface method");
}
}
使用javap来反编译生成的class文件,首先看构造方法,编译器生成的字节码中是什么指令:
JVMInstructionTest test = new JVMInstructionTest();
很明显的,构造方法,invokespecial指令
s()为类的私有实例方法,同样的也是生成invokespecial指令;p()为实例方法,应该为invokevirtual指令,而print()为静态方法,应生成invokestatic指令,反编译结果如下:
我们的测试类JVMInstructionTest实现了Runnable接口,因此对于代码:
/**
* invoke interface
*/
Runnable r = new JVMInstructionTest();
r.run();
/**
*/
对于Lambda表达式,在Java 8中Lambda表达式和默认方法时,底层会生成和使用**invokedynamic**
> list.stream().forEach(System.out::println);
![image](https://upload-images.jianshu.io/upload_images/8631313-cc770b6f8daf16ac.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
使用**MethodHandle**
**/****
* invoke dynamic
*/
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(JVMInstructionTest.class, "d",
MethodType.methodType(void.class, String.class));
System.out.println(mh);
mh.bindTo(test).invoke("a");
mh = lookup.findStatic(JVMInstructionTest.class, "ddd",
MethodType.methodType(void.class, String.class));
mh.invoke("static");
从Java语言的角度来看,MethodHandle的使用方法和效果与反射有众多相似之处,不过,他们还是有以下区别:
java.lang.reflect.Method对象远比MethodHandle机制java.lang.invoke.MethodHandle对象所包含的信息多。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
前者是方法在Java一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含执行权限等运行信息。而后者仅仅包含了于执行该方法相关的信息。用通俗的话来讲,反射是重量级的,而MethodHandle是轻量级。
由于MethodHandle是对字节码的指令调用模拟,所以理论上虚拟机在这方面做的各种优化(如方法内联),在MethodHandle上也应当可以采用类似的思路去支持。而通过反射去调用方法则不行。
最关键的一点还在于:反射API设计目标只是为了Java语言服务的,而MethodHandle则设计成可服务于所有Java虚拟机上的语言,其中也包含Java语言。
invokedynamic指令
在某种程度上,invokedynamic指令与MethodHandle机制的作用是一样的,都是为了解决原有4条指令方法分派规则固化在虚拟机之中的问题,把如何查找目标方法的决定权从虚拟机转嫁到具体用户代码中,让用户有更高的自由度。而且,他们两者的思路也是可类比的,可以把他们想象成为了达成同一个目的,一个采用上层Java代码和API来实现,另一个用字节码和Class中其他属性、常量来完成。