Java虚拟机学习08 | JVM是怎么实现invokedynamic的?(上)

https://time.geekbang.org/column/article/12564

在Java7以前,JVM提供以下4种字节码方法调用指令:

  • invokestatic:用于调用静态方法.
  • invokespecial:用于调用私有实例方法 构造器,以及使用 super 关键字调用父类的实例方法或构造器,和所实现接口的默认方法.
  • invokevirtual:用于调用非私有实例方法.
  • invokeinterface:用于调用接口方法.

在Java7的时候增加了invokedynamic来调用动态方法,该指令的调用机制抽象出调用点这一概念,允许应用程序将调用点链接到任意符合条件的方法.到了 java 8 这条指令才第一次在 java 应用,用在 lambda 表达式中. invokedynamic 与其他 invoke 指令不同的是它允许由应用级的代码来决定方法解析. 所谓应用级的代码其实是一个方法,在这里这个方法被称为引导方法(Bootstrap Method), 简称 BSM. BSM 返回一个 CallSite(调用点) 对象,这个对象就和 invokedynamic 链接在一起了。以后再执行这条 invokedynamic 指令都不会创建新的 CallSite 对象。CallSite 就是一个 MethodHandle(方法句柄)的 holder. 方法句柄指向一个调用点真正执行的方法.

方法句柄的概念

对方法句柄的理解可以是一中安全的,简单的实现反射核心功能的方式.

一个方法的构成有以下:

  • 方法名
  • 签名(参数列表和返回值)
  • 定义方法的类
  • 方法体

方法句柄的使用时根据方法签名来确定调用的方法的,并不关心方法句柄所指向方法的类名或者方法名

方法句柄是一个强类型的,能够被直接执行的引用 . 该引用可以指向常规的静态方法或者实例方法, 也可以指向构造器或者字段.当指向字段时,方法句柄实则指向包含字段访问字节码的虚构方法,语义上等价于目标字段的 getter 或者 setter 方法, 但并不会直接指向目标字段在类中对应的getter或setter方法.

方法句柄的创建是通过MrthodHandles.Lookup类来完成. 它提供了多个API,既可以通过反射API的Method来查找,也可以通过类名,方法名,方法句柄类型来查找

当使用后者这种查找方式时,用户需要区分具体的调用类型,比如说对于用 invokestatic 调用的静态方法,我们需要使用 Lookup.findStatic 方法;对于用 invokevirutal 调用的实例方法,以及用 invokeinterface 调用的接口方法,我们需要使用 findVirtual 方法;对于用 invokespecial 调用的实例方法,我们则需要使用 findSpecial 方法.

调用方法句柄,和原本对应的调用指令是一致的.也就是说,对于原本用 invokevirtual 调用的方法句柄,它也会采用动态绑定;而对于原本用 invkespecial 调用的方法句柄,它会采用静态绑定.

方法句柄同样需要验证权限,他的权限验证在句柄创建阶段完成(创建Lookup对象的时候),在实际调用过程中并不会检查权限.

由于没有运行时权限检查,与反射相比会省下检查权限的开销,同时应用程序要负责方法句柄的管理.

方法句柄的使用


public class Main {
    public String test(int i,String s){
        return s+i;
    }
    public static void main(String[] args) {
        /**
         * 创建方法句柄类型
         * 参数:目标方法返回类型,目标方法第一个参数类型,其余参数类型(可扩展数组)
         */
        MethodType mt = MethodType.methodType(String.class,int.class,String.class);
        try {

            /**
             * 创建方法句柄对象,在这里检查访问权限
             */
            MethodHandles.Lookup l = MethodHandles.lookup();
            MethodHandle mh = l.findVirtual(Main.class, "test", mt);
            /**
             * invoke调用目标方法
             */
            String s = (String) mh.invoke(Main.class.getDeclaredConstructor().newInstance(),1,"invoke");
            System.out.println(s);
            /**
             * invokeExact调用目标方法
             */
            String ss = (String) mh.invokeExact(Main.class.getDeclaredConstructor().newInstance(),1,"invokeExact");
            System.out.println(ss);
            /**
             * invokeWithArguments调用目标方法
             */
            List<Object> list = new ArrayList<>();
            list.add(Main.class.getDeclaredConstructor().newInstance());
            list.add(1);
            list.add("invokeWithArguments");

            String sss = (String) mh.invokeWithArguments(list);
            System.out.println(sss);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

方法句柄的实现

当调用方法句柄时(执行invokeExact方法),Java虚拟机会将调用送到一个共享的,与方法句柄类型相关的特俗适配器中,这个适配器是一个LambdaForm.

在这个适配器中,它会调用 Invokers.checkExactType 方法来检查参数类型,然后调用 Invokers.checkCustomized 方法.后者会在方法句柄的执行次数超过一个阈值时进行优化(对应参数 -Djava.lang.invoke.MethodHandle.CUSTOMIZE_THRESHOLD,默认值为 127).最后,它会调用方法句柄的 invokeBasic 方法.

Java 虚拟机同样会对 invokeBasic 调用做特殊处理,这会将调用至方法句柄本身所持有的适配器中. 这个适配器同样是一个 LambdaForm.

这个适配器将获取方法句柄中的 MemberName 类型的字段,并且以它为参数调用 linkToStatic 方法.Java 虚拟机也会对 linkToStatic 调用做特殊处理,它将根据传入的 MemberName 参数所存储的方法地址或者方法表索引,直接跳转至目标方法.

LambdaForm:

Make a new intermediate representation, called lambda form, for method handles that is (a) directly executable and (b) directly and simply reducible to bytecodes and/or JIT IR. Implement all method handle operations, and invokedynamic call sites, using lambda forms.

猜你喜欢

转载自blog.csdn.net/qq_34332035/article/details/87905037
今日推荐