26 January 2020

以下内容基于JDK8

随着JVM平台不断发展,有一些动态类型语言都有了JVM平台的实现。但之前JVM平台的设计都是按照静态类型语言来搞的,而动态类型语言并没有这些静态类型信息,因此编译动态类型语言时颇为不便。此外,当时的JVM并不能很好支持动态修改已有的类和方法,而这个功能对于一些动态类型语言来说却很常用。例如下面的JRuby代码:

def foo(a, b)
    puts "foo method: #{a + b}"
end

def bar
    foo(1, 2)
end

bar

def foo(a, b)
    puts "foo method modified: #{a * b}"
end

bar

这个代码里面方法foo有两个定义,两次执行bar方法时会产生不同的结果,而且方法foo的形参没有类型信息,+这个操作的具体实现无法编译器决定。

以上就是JSR 292要解决的问题,解决方式就是在修改JVM规范,添加invokedynamic指令。

在JSR 292之前,函数调用的指令共有4个,分别是invokestaticinvokespecialinvokeinterfaceinvokevirtual,他们的任务大致如下:

  1. 校验目标函数的对象类型是否匹配(invokestatic不做这一步)
  2. 校验目标函数的参数类型是否匹配
  3. 在Java类中查找目标方法
  4. 缓存查找到的方法
  5. 调用方法

平时调用函数时,实际上可以分为几个步骤:

  1. 定义函数
  2. 准备函数参数
  3. 查找要调用的函数
  4. 调用函数

以往的invoke*系列指令是针对静态类型语言设计的,定义函数时会提供足够的类型信息,因此能够把 查找要调用的函数调用函数这两件事打包一起干了;而在动态类型语言中,函数的定义往往没有足够的类型信息,无法在JVM准确找到目标方法,需要开发者自己实现一套调用逻辑。以JRuby为例,函数调用的执行过程大致是这样的:

public abstract class CachingCallSite extends CallSite {
    protected CacheEntry cache = CacheEntry.NULL_CACHE;
    private final String methodName = ...
    public IRubyObject call(...) {
        RubyClass selfType = getClass(self);
        CacheEntry cache = this.cache;
        if (CacheEntry.typeOk(cache, selfType)) {
            return cache.method.call(...);
        }
        return cacheAndCall(...);
    } 
    
    protected IRubyObject cacheAndCall(...) {
        CacheEntry entry = selfType.searchWithCache(methodName);
        DynamicMethod method = entry.method;
        if (methodMissing(method, caller)) {
            return callMethodMissing(context, self, method);
        }
        updateCache(entry);
        return method.call(context. self, selfType, methodName);
    }
}

这里面,需要指定函数查找策略,例如selfType.searchWithCache(methodName),然后再执行函数调用method.call(context. self, selfType, methodName)。每次函数调用时,都需要做这一坨事情,效率自然不行,而且隔着这么一坨代码,也无法对JRuby的代码应用JIT优化。

在JSR 292之后,通过invokedynamic指令,开发者可以通过 bootstrap method设置CallSite,这个CallSite中包含了要调用的函数实现(MethodHandle)。JVM在执行invokedynamic指令时,会调用CallSite中的指定的方法实现。

invokedynamic使语言开发者可以自行实现一套方法查找机制(bootstrap method),然后将找到了方法和方法接收者绑定,完成方法调用。

关于invokedynamic的几个名词:

  • call site: 调用函数的位置,在Java中的实现是CallSite
  • dynamic call site: 每个出现invokedynamic指令的地方都是一个dynamic call site
  • call site的target: 该位置要调用的具体函数,指CallSite类的target字段,即MethodHandle对象
  • bootstrap method: 创建 call site的函数
  • method handle: 类似于C语言的函数指针,包含了形参和返回值的类型信息,没有实现,没有函数名

Java8中,在使用lanbda表达式的时候,会生成invokedynamic的指令。如下所示:

package com.example.demo.lambda;

import java.util.function.Consumer;

public class LambdaDemo {

    public static void main(String[] args) {
        Consumer<Integer> c1 = s -> System.out.println(s);
        c1.accept(1);
        Consumer<Long> c2 = s -> System.out.println(s);
        c2.accept(2L);
    }

}

上面代码生成的字节码如下所示:

Classfile /Users/didi/workspace/eclipse/eclipse/demo/bin/main/com/example/demo/lambda/    LambdaDemo.class
  Last modified 2020-1-20; size 1773 bytes
  MD5 checksum 4261a196e29c5d317e63a4483548ce1c
  Compiled from "LambdaDemo.java"
public class com.example.demo.lambda.LambdaDemo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/example/demo/lambda/LambdaDemo
   #2 = Utf8               com/example/demo/lambda/LambdaDemo
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/example/demo/lambda/LambdaDemo;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = InvokeDynamic      #0:#17         // #0:accept:()Ljava/util/function/Consumer;
  #17 = NameAndType        #18:#19        // accept:()Ljava/util/function/Consumer;
  #18 = Utf8               accept
  #19 = Utf8               ()Ljava/util/function/Consumer;
  #20 = Methodref          #21.#23        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  #21 = Class              #22            // java/lang/Integer
  #22 = Utf8               java/lang/Integer
  #23 = NameAndType        #24:#25        // valueOf:(I)Ljava/lang/Integer;
  #24 = Utf8               valueOf
  #25 = Utf8               (I)Ljava/lang/Integer;
  #26 = InterfaceMethodref #27.#29        // java/util/function/Consumer.accept:(Ljava/lang/    Object;)V
  #27 = Class              #28            // java/util/function/Consumer
  #28 = Utf8               java/util/function/Consumer
  #29 = NameAndType        #18:#30        // accept:(Ljava/lang/Object;)V
  #30 = Utf8               (Ljava/lang/Object;)V
  #31 = InvokeDynamic      #1:#17         // #1:accept:()Ljava/util/function/Consumer;
  #32 = Long               2l
  #34 = Methodref          #35.#37        // java/lang/Long.valueOf:(J)Ljava/lang/Long;
  #35 = Class              #36            // java/lang/Long
  #36 = Utf8               java/lang/Long
  #37 = NameAndType        #24:#38        // valueOf:(J)Ljava/lang/Long;
  #38 = Utf8               (J)Ljava/lang/Long;
  #39 = Utf8               args
  #40 = Utf8               [Ljava/lang/String;
  #41 = Utf8               c1
  #42 = Utf8               Ljava/util/function/Consumer;
  #43 = Utf8               c2
  #44 = Utf8               LocalVariableTypeTable
  #45 = Utf8               Ljava/util/function/Consumer<Ljava/lang/Integer;>;
  #46 = Utf8               Ljava/util/function/Consumer<Ljava/lang/Long;>;
  #47 = Utf8               lambda$0
  #48 = Utf8               (Ljava/lang/Integer;)V
  #49 = Fieldref           #50.#52        // java/lang/System.out:Ljava/io/PrintStream;
  #50 = Class              #51            // java/lang/System
  #51 = Utf8               java/lang/System
  #52 = NameAndType        #53:#54        // out:Ljava/io/PrintStream;
  #53 = Utf8               out
  #54 = Utf8               Ljava/io/PrintStream;
  #55 = Methodref          #56.#58        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #56 = Class              #57            // java/io/PrintStream
  #57 = Utf8               java/io/PrintStream
  #58 = NameAndType        #59:#30        // println:(Ljava/lang/Object;)V
  #59 = Utf8               println
  #60 = Utf8               s
  #61 = Utf8               Ljava/lang/Integer;
  #62 = Utf8               lambda$1
  #63 = Utf8               (Ljava/lang/Long;)V
  #64 = Utf8               Ljava/lang/Long;
  #65 = Utf8               SourceFile
  #66 = Utf8               LambdaDemo.java
  #67 = Utf8               BootstrapMethods
  #68 = Methodref          #69.#71        // java/lang/invoke/LambdaMetafactory.metafactory:    (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;    Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)    Ljava/lang/invoke/CallSite;
  #69 = Class              #70            // java/lang/invoke/LambdaMetafactory
  #70 = Utf8               java/lang/invoke/LambdaMetafactory
  #71 = NameAndType        #72:#73        // metafactory:(Ljava/lang/invoke/    MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/    MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/    CallSite;
  #72 = Utf8               metafactory
  #73 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/    lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/    lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #74 = MethodHandle       #6:#68         // invokestatic java/lang/invoke/    LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;    Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;    Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #75 = MethodType         #30            //  (Ljava/lang/Object;)V
  #76 = Methodref          #1.#77         // com/example/demo/lambda/LambdaDemo.lambda$0:    (Ljava/lang/Integer;)V
  #77 = NameAndType        #47:#48        // lambda$0:(Ljava/lang/Integer;)V
  #78 = MethodHandle       #6:#76         // invokestatic com/example/demo/lambda/    LambdaDemo.lambda$0:(Ljava/lang/Integer;)V
  #79 = MethodType         #48            //  (Ljava/lang/Integer;)V
  #80 = MethodType         #30            //  (Ljava/lang/Object;)V
  #81 = Methodref          #1.#82         // com/example/demo/lambda/LambdaDemo.lambda$1:    (Ljava/lang/Long;)V
  #82 = NameAndType        #62:#63        // lambda$1:(Ljava/lang/Long;)V
  #83 = MethodHandle       #6:#81         // invokestatic com/example/demo/lambda/    LambdaDemo.lambda$1:(Ljava/lang/Long;)V
  #84 = MethodType         #63            //  (Ljava/lang/Long;)V
  #85 = Utf8               InnerClasses
  #86 = Class              #87            // java/lang/invoke/MethodHandles$Lookup
  #87 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #88 = Class              #89            // java/lang/invoke/MethodHandles
  #89 = Utf8               java/lang/invoke/MethodHandles
  #90 = Utf8               Lookup
{
  public com.example.demo.lambda.LambdaDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/example/demo/lambda/LambdaDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: invokedynamic #16,  0             // InvokeDynamic #0:accept:()Ljava/util/    function/Consumer;
         5: astore_1
         6: aload_1
         7: iconst_1
         8: invokestatic  #20                 // Method java/lang/Integer.valueOf:(I)Ljava/    lang/Integer;
        11: invokeinterface #26,  2           // InterfaceMethod java/util/function/    Consumer.accept:(Ljava/lang/Object;)V
        16: invokedynamic #31,  0             // InvokeDynamic #1:accept:()Ljava/util/    function/Consumer;
        21: astore_2
        22: aload_2
        23: ldc2_w        #32                 // long 2l
        26: invokestatic  #34                 // Method java/lang/Long.valueOf:(J)Ljava/lang/    Long;
        29: invokeinterface #26,  2           // InterfaceMethod java/util/function/    Consumer.accept:(Ljava/lang/Object;)V
        34: return
      LineNumberTable:
        line 8: 0
        line 9: 6
        line 10: 16
        line 11: 22
        line 12: 34
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      35     0  args   [Ljava/lang/String;
            6      29     1    c1   Ljava/util/function/Consumer;
           22      13     2    c2   Ljava/util/function/Consumer;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            6      29     1    c1   Ljava/util/function/Consumer<Ljava/lang/Integer;>;
           22      13     2    c2   Ljava/util/function/Consumer<Ljava/lang/Long;>;

  private static void lambda$0(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #49                 // Field java/lang/System.out:Ljava/io/    PrintStream;
         3: aload_0
         4: invokevirtual #55                 // Method java/io/PrintStream.println:(Ljava/    lang/Object;)V
         7: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0     s   Ljava/lang/Integer;

  private static void lambda$1(java.lang.Long);
    descriptor: (Ljava/lang/Long;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #49                 // Field java/lang/System.out:Ljava/io/    PrintStream;
         3: aload_0
         4: invokevirtual #55                 // Method java/io/PrintStream.println:(Ljava/    lang/Object;)V
         7: return
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0     s   Ljava/lang/Long;
}
SourceFile: "LambdaDemo.java"
BootstrapMethods:
  0: #74 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/    MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/    MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/    CallSite;
    Method arguments:
      #75 (Ljava/lang/Object;)V
      #78 invokestatic com/example/demo/lambda/LambdaDemo.lambda$0:(Ljava/lang/Integer;)V
      #79 (Ljava/lang/Integer;)V
  1: #74 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/    MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/    MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/    CallSite;
    Method arguments:
      #80 (Ljava/lang/Object;)V
      #83 invokestatic com/example/demo/lambda/LambdaDemo.lambda$1:(Ljava/lang/Long;)V
      #84 (Ljava/lang/Long;)V
InnerClasses:
     public static final #90= #86 of #88; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

上面的字节码中,通过invokedynamic指令,指明了要调用的BootstrapMethods属性的索引值,然后调用对应的方法生成 dynamic call site

Resources