JVM invokedynamic调用指令

本文详细介绍了JVM的invokedynamic指令,包括方法句柄的概念、创建与操作,以及invokedynamic如何实现动态方法调用。通过对Lambda表达式的深入解析,揭示了Java8如何利用invokedynamic提升性能。文中还包含了方法句柄的性能对比、Lambda表达式的编译过程和性能测试,展示了Lambda捕获变量时的内在机制。
摘要由CSDN通过智能技术生成

转载请注明链接: https://blog.csdn.net/feather_wch/article/details/82719313

invokedynamic指令到底是什么?方法句柄又是什么?Java8是如何利用invokedynamic实现Lambda表达式的?
本文对invokedynamic的知识点进行归纳总结。

JVM invokedynamic调用指令

版本号:2018/09/16-1(0:00)


invokedynamic


基础-方法句柄(32)

赛马问题

1、如何让非马的类能和马一样参加赛马比赛?

    class Horse{
        public void race(){
            System.out.println("马在赛跑");
        }
    }

    class Duck{
        public void race(){
            System.out.println("鸭子在赛跑");
        }
    }

    class Deer{
        public void race(){
            System.out.println("马在赛跑");
        }
    }
  1. 第一种方法:将非马的类型包装成马类进行赛跑。
  2. 第二种方法:通过反射机制,查找并调用各个类型中的赛跑方法。

2、invokedynamic指令的作用?

  1. Java7引入的新指令
  2. 该指令抽象出调用点这一个概念
  3. 允许应用程序将调用点链接至任何符合条件的方法上
  4. 解决了赛马问题,但是比包装和反射更高效。

3、invokedynamic的应用场景

lambda表达式

4、invokedynamic底层机制的基石:方法句柄

方法句柄

5、方法句柄是什么?

  1. MethodHandle-方法句柄
  2. 是一种更加底层、更加灵活的方法抽象(Java7引入)
  3. 方法句柄是一种强类型、并且能够被直接执行的引用。
  4. 该引用可以指向常规的静态方法、实例方法、构造器、字段。
  5. 当指向字段时,方法句柄实则指向包含字段访问字节码的虚构方法,语义上等价于目标字段的 getter 或者 setter 方法。(但不会直接指向目标字段所在类中的 getter/setter)

6、方法句柄的类型是什么?

  1. MethodType-方法句柄类型
  2. 方法句柄类型由所指向方法的参数类型、返回类型组成的。
  3. 是用来 确认方法句柄是否适配的唯一关键。
  4. 当使用方法句柄时,并不需要关心方法句柄所指向方法的类名或者方法名

7、如果一个兔子的赛跑方法和睡觉方法的参数类型、返回类型一致,如何判断是哪一个方法?

  1. 兔子传递的方法句柄,将无法判断是哪一个方法。
创建

8、方法句柄的创建?

  1. 方法句柄的创建需要通过 MethodHandles.Lookup 类来完成。
  2. Lookup提供了多个API,可以使用反射的Method来查找方法句柄。
  3. 也可以使用类、方法名、方法句柄类型来查找。

1-Horse类:

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;

public class Horse {
   
    public void race(){
        System.out.println("马在赛跑");
    }
    // 返回Lookup类
    public static Lookup lookup(){
        return MethodHandles.lookup();
    }
}

2-通过反射的Method来获取方法句柄(MethodHandle)

        // 1、获取到Lookup
        MethodHandles.Lookup lookup = Horse.lookup();
        // 2、获取到反射的Method
        Method method = Horse.class.getDeclaredMethod("race", Void.class);
        // 3、获取到方法句柄
        MethodHandle methodHandle = lookup.unreflect(method);

3-通过类、方法名、方法句柄类型,来获取MethodHandle

        // 1、获取到Lookup
        MethodHandles.Lookup lookup = Horse.lookup();
        // 2、方法句柄类型,第一个是返回值类型,第二个是参数类型
        MethodType methodType = MethodType.methodType(Void.class, Void.class);
        // 3、创建方法句柄,依次使用类、方法名、方法句柄类型。
        MethodHandle methodHandle = lookup.findVirtual(Horse.class, "race", methodType);

9、Lookup有哪些创建MethodHandle的API?

  1. 对于invokestatic调用指令的静态方法:使用lookup.findStatic()
  2. 对于invokevirutal、invokeinterface调用指令的方法:使用lookup.findVirtual()
  3. 对于invokespecial调用指令的实例方法:使用lookup.findSpecial()

10、方法句柄(MethodHandle)的权限问题?

  1. 权限问题上和反射API不同
  2. MethodHandle的权限检查是在句柄创建阶段完成
  3. 实际调用过程中,JVM不会检查方法句柄的权限,如果被多次调用,性能比反射要高。
  4. 方法句柄没有运行时检查权限,因此app需要负责方法句柄的管理。

11、方法句柄和反射的性能对比

  1. 方法句柄的权限检查在创建阶段完成
  2. 反射的权限检查是处于运行时。
  3. JVM不会去检查方法句柄的权限。
  4. 因此在多次调用的情况下,方法句柄会节省出权限检查的开销。

12、方法句柄的权限是如何判定的?

  1. 方法句柄的访问权限不取决于MethodHandle的创建位置
  2. 而是取决于Lookup对象的创建位置。
  3. 如果Lookup对象在private字段所在类中获取,就拥有了对该private字段的访问权限。
  4. 这样在所在类的外边,也可以通过该Lookup对象创建该private字段的getter或者setter。
操作

13、方法句柄的调用方法?

分为两种:
1. 需要严格匹配参数类型的invokeExact
1. 自动适配参数类型的invoke

14、invokeExact的使用

  1. 对匹配参数类型非常严格
  2. 如果MethodHandle接收一个Object类型的参数,传入String作为实参就会在运行时抛出异常
  3. 必须要显式转换类型,让形参和实参一致。
        methodHandle.invokeExact(str);
        methodHandle.invokeExact((Object)str);

15、重载方法时的显式转化类型是为了什么?

  1. 在显式转化后,参数的声明类型发生了改变,从而匹配到不同的方法描述符
  2. 根据方法描述符的不同,选取到不同的目标方法。
  3. 调用方法句柄也是一样的道理,并且涉及到签名多态性-signature polymorphism的概念

16、方法句柄API的注解@PolymorphicSignature是什么?这个方法描述符和invokeExact的关系?

  1. 通过该注解,Java编译器在遇到这些方法调用时,会根据传入参数的声明类型来生成方法描述符
  2. 而不是采用目标方法所声明的描述符。
  3. invokeExact会严格要求方法句柄的类型和调用时的方法描述符是否匹配
  4. 如下面的例子:就是根据传入参数的声明类型来生成描述符
methodHandle.invokeExact(str);
// 描述符:MethodHandle.invokeExact:(Ljava/lang/String;)V

methodHandle.invokeExact((Object)str);
// 描述符: MethodHandle.invokeExact:(Ljava/lang/Object;)V

17、什么是签名多态性?

  1. 方法用@PolymorphicSignature注解进行标记
  2. Java编译器在调用这些方法时,会根据传入参数的声明类型来生成方法描述符

18、invoke的使用和内部原理

    methodHandle.invoke(str);
  1. 该方法自动适配参数类型,也是一个签名多态性的方法
  2. 调用方法句柄前,会调用MethodHandle.asType()生成一个适配器方法句柄,对传入的参数进行适配。
  3. 方法句柄的返回值,也会进行适配然后返回给调用者。

19、方法句柄的增加、删除、修改参数的操作

本质都是通过生成另一个方法句柄来实现
1. 修改操作:MethodHandle.asType()
1. 删除操作: MethodHandles.dropArguments(), 将传入的部分参数直接抛弃,然后调用另一个方法句柄。
1. 增加操作:MethodHandle.bindTo(), 在传入的参数中增加额外的参数,再调用另一个方法句柄。

20、Java 8中捕获类型的Lambda表达式是如何实现的?

利用的方法句柄的增加操作来实现的。

21、MethodHandle的增加操作还可以实现方法的柯里化

  1. 一个指向f(x,y)的句柄,将x绑定为4,生成一个方法句柄g(y)=f(4, y)
  2. 执行过程中,调用g(y)方法句柄,就会默认去调用f(4,y)的方法句柄

22、MethodHandle.bindTo()限制了只能使用引用类型,普遍是用来绑定this的,为什么能用来实现柯里化(将int x, int y 的第一个参数指定为常数4)?

  1. bindTo的确限制了引用类型,但是方法句柄不区分调用者和参数。
  2. 因此依然可以用做其他效果:可以用Integer,然后使用静态方法,或者在使用virtual方法时将bindTo返回的方法句柄再bindTo一个Integer.valueOf(4)。
  3. BoundMethodHandle里有很多非public的bindArgumentXXX()方法,和这些有相关性。

方法句柄的实现

23、方法句柄的使用实例

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;

public class Horse {
   
    public static void race(){
        System.out.println("马在赛跑");
    }
    // 返回Lookup类
    public static Lookup lookup(){
        return MethodHandles.lookup();
    }
}
// 1、获取到Lookup
MethodHandles.Lookup lookup = Horse.lookup();
// 2、方法句柄类型,第一个是返回值类型,第二个是参数类型
MethodType methodType = MethodType.methodType(void.class);
// 3、创建方法句柄,依次使用类、方法名、方法句柄类型。
MethodHandle methodHandle = lookup.findStatic(Horse.class, "race", methodType);

// 调用
methodHandle.invoke();
// 打印出
马在赛跑

24、查看方法句柄调用invoke、invokeExact的栈轨迹

public class Horse {
   
    public static void race(){
      // 打印栈轨迹
        new Exception().printStackTrace();
        System.out.println("马在赛跑");
    }
    // xxx
}

栈信息

java.lang.Exception
    at Horse
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猎羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值