JVM指令之invokestatic,invokespecial,invokeinterface,invokevirtual,invokedynamic

指令说明
invokeinterface用以调用接口方法,在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。(Invoke interface method)
invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(Invoke instance method; dispatch based on class)
invokestatic用以调用类方法(Invoke a class (static) method )
invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。(Invoke instance method; special handling for superclass, private, and instance initialization method invocations )
invokedynamicJDK1.7新加入的一个虚拟机指令,相比于之前的四条指令,他们的分派逻辑都是固化在JVM内部,而invokedynamic则用于处理新的方法分派:它允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,从而达到动态语言的支持。(Invoke dynamic method)

我们看一段代码来说明上述虚拟机指令的运用。

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();
        /**

其对应生成的指令应为invokeinterface,反编译结果如下:
这里写图片描述

对于Lambda表达式,在Java 8中Lambda表达式和默认方法时,底层会生成和使用invokedynamic

list.stream().forEach(System.out::println);

我们看下生成的指令代码使用了invokedynamic
这里写图片描述

使用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虚拟机》:

从Java语言的角度来看,MethodHandle的使用方法和效果与反射有众多相似之处,不过,他们还是有以下区别:

  • 反射中的java.lang.reflect.Method对象远比MethodHandle机制中的java.lang.invoke.MethodHandle对象所包含的信息多。前者是方法在Java一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含执行权限等运行信息。而后者仅仅包含了于执行该方法相关的信息。用通俗的话来讲,反射是重量级的,而MethodHandle是轻量级。

  • 由于MethodHandle是对字节码的指令调用模拟,所以理论上虚拟机在这方面做的各种优化(如方法内联),在MethodHandle上也应当可以采用类似的思路去支持。而通过反射去调用方法则不行。

  • 最关键的一点还在于:反射API设计目标只是为了Java语言服务的,而MethodHandle则设计成可服务于所有Java虚拟机上的语言,其中也包含Java语言。

MethodHandle VS Reflect

        /**
         * method handle
         */
        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");

        /**
         * reflect
         */
        Class cls = Class.forName("erasure.JVMInstructionTest");
        Object obj = cls.newInstance();
        Method method = cls.getMethod("d", String.class);
        method.invoke(obj, "reflect");

invokedynamic指令

在某种程度上,invokedynamic指令与MethodHandle机制的作用是一样的,都是为了解决原有4条指令方法分派规则固化在虚拟机之中的问题,把如何查找目标方法的决定权从虚拟机转嫁到具体用户代码中,让用户有更高的自由度。而且,他们两者的思路也是可类比的,可以把他们想象成为了达成同一个目的,一个采用上层Java代码和API来实现,另一个用字节码和Class中其他属性、常量来完成。

  • 7
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值