以下内容基于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个,分别是invokestatic
,invokespecial
,invokeinterface
和invokevirtual
,他们的任务大致如下:
- 校验目标函数的对象类型是否匹配(
invokestatic
不做这一步) - 校验目标函数的参数类型是否匹配
- 在Java类中查找目标方法
- 缓存查找到的方法
- 调用方法
平时调用函数时,实际上可以分为几个步骤:
- 定义函数
- 准备函数参数
- 查找要调用的函数
- 调用函数
以往的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。