invokedynamic最初的工作至少始于2007年,而第一次成功的动态调用发生在2008年8月26日。这比Oracle收购Sun还要早,按照大多数开发人员的标准,这个特性的研发已经持续了相当长的时间。
值得注意的是,从Java 1.0到现在,invokedynamic是第一个新加入的Java字节码,它与已有的字节码invokevirtual、invokestatic、invokeinterface和invokespecial组合在了一起。已有的这四个操作码实现了Java开发人员所熟知的所有形式的方法分派(dispatch):
invokevirtual——对实例方法的标准分派
invokestatic——用于分派静态方法
invokeinterface——用于通过接口进行方法调用的分派
invokespecial——当需要进行非虚(也就是“精确”)分派时会用到
对Java方法的所有调用都编译成了四个操作码中的某一个,那么问题就来了——invokedynamic是做什么的,它对于Java开发人员有什么用处呢?
这个特性的主要目标在于创建一个字节码,用于处理新型的方法分派——它的本质是允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断。这样的话,相对于Java平台之前所提供的编程风格,允许语言和框架的编写人员支持更加动态的编码风格。
它的目的在于由用户代码通过方法句柄API(method handles API)在运行时确定如何分派,同时避免反射带来的性能惩罚和安全问题。实际上,invokedynamic所宣称的目标就是一旦该特性足够成熟,它的速度要像常规的方法分派(invokevirtual)一样快。
当Java 7发布的时候,JVM就已经支持执行新的字节码了,但是不管提交什么样的Java代码,javac都不会产生包含invokedynamic的字节码。这项特性用来支持JRuby和其他运行在JVM上的动态语言。
在Java 8中,这发生了变化,在实现lambda表达式和默认方法时,底层会生成和使用invokedynamic,它同时还会作为Nashorn的首选分派机制。但是,对于Java应用的开发人员来说,依然没有直接的方式实现完全的动态方法处理(resolution)。也就是说,Java语言并没有提供关键字或库来创建通用的invokedynamic调用点(call site)。这意味着,尽管这种机制的功能非常强大,但它对于大多数的Java开发人员来说依然有些陌生。
inDy(invokedynamic)是 java 7 引入的一条新的虚拟机指令,这是自 1.0 以来第一次引入新的虚拟机指令。到了 java 8 这条指令才第一次在 java 应用,用在 lambda 表达式中。 indy 与其他 invoke 指令不同的是它允许由应用级的代码来决定方法解析。所谓应用级的代码其实是一个方法,在这里这个方法被称为引导方法(Bootstrap Method),简称 BSM。BSM 返回一个 CallSite(调用点) 对象,这个对象就和 inDy 链接在一起了。以后再执行这条 inDy 指令都不会创建新的 CallSite 对象。CallSite 就是一个 MethodHandle(方法句柄)的 holder。方法句柄指向一个调用点真正执行的方法。
方法句柄简介
要让invokedynamic正常运行,一个核心的概念就是方法句柄(method handle)。它代表了一个可以从invokedynamic调用点进行调用的方法。这里的基本理念就是每个invokedynamic指令都会与一个特定的方法关联(也就是引导方法或BSM)。当解释器(interpreter)遇到invokedynamic指令的时候,BSM会被调用。它会返回一个对象(包含了一个方法句柄,方法句柄类似函数指针),这个对象表明了调用点要实际执行哪个方法。
在一定程度上,这与反射有些类似,但是反射有它的局限性,这些局限性使它不适合与invokedynamic协作使用。Java 7 API中加入了java.lang.invoke.MethodHandle(及其子类),通过它们来代表invokedynamic指向的方法。为了实现操作的正确性,MethodHandle会得到JVM的一些特殊处理。
方法类型
一个Java方法可以视为由四个基本内容所构成:
名称
签名(包含返回类型)
定义它的类
实现方法的字节码
方法句柄首先需要的一个构建块就是表达方法签名的方式,以便于查找。在Java 7引入的Method Handles API中,这个角色是由java.lang.invoke.MethodType类来完成的,它使用一个不可变的实例来代表签名。要获取MethodType,我们可以使用methodType()工厂方法。这是一个参数可变(variadic)的方法,以class对象作为参数。
第一个参数所使用的class对象,对应着签名的返回类型;剩余参数中所使用的class对象,对应着签名中方法参数的类型。例如:
//toString()的签名
MethodType mtToString = MethodType.methodType(String.class);
// setter方法的签名
MethodType mtSetter = MethodType.methodType(void.class, Object.class);
// Comparator中compare()方法的签名
MethodType mtStringComparator = MethodType.methodType(int.class, String.class, String.class);
现在我们就可以使用MethodType,再组合方法名称以及定义方法的类来查找方法句柄。要实现这一点,我们需要调用静态的MethodHandles.lookup()方法。这样的话,会给我们一个“查找上下文(lookup context)”,这个上下文基于当前正在执行的方法(也就是调用lookup()的方法)的访问权限。
查找上下文对象有一些以“find”开头的方法,例如,findVirtual()、findConstructor()、findStatic()等。这些方法将会返回实际的方法句柄,需要注意的是,只有在创建查找上下文的方法能够访问(调用)被请求方法的情况下,才会返回句柄
public MethodHandle getToStringMH() {
MethodHandle mh = null;
MethodType mt = MethodType.methodType(String.class);
MethodHandles.Lookup lk = MethodHandles.lookup();
try {
mh = lk.findVirtual(getClass(), "toString", mt);
} catch (NoSuchMethodException | IllegalAccessException mhx) {
throw (AssertionError)new AssertionError().initCause(mhx);
}
return mh;
}
MethodHandle中有两个方法能够触发对方法句柄的调用,那就是invoke()和invokeExact()。这两个方法都是以接收者(receiver)和调用变量作为参数,所以它们的签名为:
public final Object invoke(Object... args) throws Throwable;
public final Object invokeExact(Object... args) throws Throwable;
两者的区别在于,invokeExact()在调用方法句柄时会试图严格地直接匹配所提供的变量。而invoke()与之不同,在需要的时候,invoke()能够稍微调整一下方法的变量。invoke()会执行一个asType()转换,它会根据如下的这组规则来进行变量的转换:
如果需要的话,原始类型会进行装箱操作
如果需要的话,装箱后的原始类型会进行拆箱操作
如果必要的话,原始类型会进行扩展
void返回类型会转换为0(对于返回原始类型的情况),而对于预期得到引用类型的返回值的地方,将会转换为null
null值会被视为正确的,不管静态类型是什么都可以进行传递
接下来,我们看一下考虑上述规则的简单调用样例:
Object rcvr = "a";
try {
MethodType mt = MethodType.methodType(int.class);
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle mh = l.findVirtual(rcvr.getClass(), "hashCode", mt);
int ret;
try {
ret = (int)mh.invoke(rcvr);
System.out.println(ret);
} catch (Throwable t) {
t.printStackTrace();
}
} catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
在更为复杂的样例中,方法句柄能够以更清晰的方式来执行与核心反射功能相同的动态编程任务。除此之外,在设计之初,方法句柄就与JVM底层的执行模型协作地更好.
方法句柄与invokedynamic
invokedynamic指令通过引导方法(bootstrap method,BSM)机制来使用方法句柄。与invokevirtual指令不同,invokedynamic指令没有接收者对象。相反,它们的行为类似于invokestatic,会使用BSM来返回一个CallSite类型的对象。这个对象包含一个方法句柄(称之为“target”),它代表了当前invokedynamic指令要执行的方法。
当包含invokedynamic的类加载时,调用点会处于“unlaced”状态,在BSM返回之后,得到的CallSite和方法句柄会让调用点处于“laced”状态。
BSM的签名大致会如下所示(注意,BSM的名称是任意的):
static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type);
如果你希望创建包含invokedynamic的代码,那么我们需要使用一个字节码操纵库(因为Java语言本身并不包含我们所需的构造)。在本文剩余的内容中,我们将会使用ASM库来生成包含invokedynamic指令的字节码。从Java应用程序的角度来看,它们看起来就像是常规的类文件(当然,它们没有相关的Java源码表述)。Java代码会将其视为“黑盒”,不过我们可以调用方法并使用invokedynamic及其相关的功能。
下面,我们来看一下基于ASM的类,它会使用invokedynamic指令来生成“Hello World”。
package jvm.excecution_engine;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static jdk.internal.org.objectweb.asm.Opcodes.V1_7;
import static jdk.nashorn.internal.runtime.regexp.joni.constants.StackType.RETURN;
import static sun.tools.java.RuntimeConstants.ACC_PUBLIC;
public class InvokeDynamicCreator {
public static void main(final String[] args) throws Exception {
final String outputClassName = "Dynamic";
try (FileOutputStream fos
= new FileOutputStream(new File("./" + outputClassName + ".class"))) {
fos.write(dump(outputClassName, "bootstrap", "()V"));
}
}
public static byte[] dump(String outputClassName, String bsmName, String targetMethodDescriptor)
throws Exception {
final ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
// 为引导类搭建基本的元数据
cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, outputClassName, null, "java/lang/Object", null);
// 创建标准的void构造器
mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// 创建标准的main方法
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class);
Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "kathik/InvokeDynamicCreator", bsmName,
mt.toMethodDescriptorString());
mv.visitInvokeDynamicInsn("runDynamic", targetMethodDescriptor, bootstrap);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
private static void targetMethod() {
System.out.println("Hello World!");
}
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
// 需要使用lookupClass(),因为这个方法是静态的
final Class currentClass = lookup.lookupClass();
final MethodType targetSignature = MethodType.methodType(void.class);
final MethodHandle targetMH = lookup.findStatic(currentClass, "targetMethod", targetSignature);
return new ConstantCallSite(targetMH.asType(type));
}
}
这个代码分为两部分,第一部分使用ASM Visitor API来创建名为Dynamic的类文件。注意,核心的调用是visitInvokeDynamicInsn()。第二部分包含了要捆绑到调用点中的目标方法,并且还包括invokedynamic指令所需的BSM。
注意,上述的方法是位于InvokeDynamicCreator类中的,而不是所生成的kathik.Dynamic类的一部分。这意味着,在运行时,InvokeDynamicCreator必须也要和kathik.Dynamic一起位于类路径中,否则的话,就会无法找到方法。
当InvokeDynamicCreator运行时,它会创建一个新的类文件Dynamic.class,这个文件中包含了invokedynamic指令,通过在这个类上执行javap,我们可以看到这一点:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokedynamic #20, 0 // InvokeDynamic #0:runDynamic:()V
5: return
这个样例阐述了invokedynamic最简单的使用场景,它会使用一个特定的常量CallSite对象。这意味着BSM(和lookup)只会执行一次,所以后续的调用会很快。
java8 lambda 表达式
lambda 表达式 是怎么使用 inDy 呢?以一段简单的代码为例
public class LambdaTest {
public static void main(String[] args) {
Runnable r = () -> System.out.println(Arrays.toString(args));
r.run();
}
}
用 javap -v -p LambdaTest 查看字节码,可以发现寥寥几行 java 代码生成的字节码却不少,单单常量池常量就有 66 个之多。
Classfile ./com/company/LambdaTest.class
Last modified 2017-10-11; size 1296 bytes
MD5 checksum 7ac0bf40633d31a57673577fde0a699d
Compiled from "LambdaTest.java"
public class com.company.LambdaTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#25 // java/lang/Object."":()V
#2 = InvokeDynamic #0:#30 // #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
#3 = InterfaceMethodref #31.#32 // java/lang/Runnable.run:()V
#4 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #35.#36 // java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
#6 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #39 // com/company/LambdaTest
#8 = Class #40 // java/lang/Object
#9 = Utf8
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/company/LambdaTest;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
#19 = Utf8 [Ljava/lang/String;
#20 = Utf8 r
#21 = Utf8 Ljava/lang/Runnable;
#22 = Utf8 lambda$main$0
#23 = Utf8 SourceFile
#24 = Utf8 LambdaTest.java
#25 = NameAndType #9:#10 // "":()V
#26 = Utf8 BootstrapMethods
#27 = MethodHandle #6:#41 // 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;
#28 = MethodType #10 // ()V
#29 = MethodHandle #6:#42 // invokestatic com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#30 = NameAndType #43:#44 // run:([Ljava/lang/String;)Ljava/lang/Runnable;
#31 = Class #45 // java/lang/Runnable
#32 = NameAndType #43:#10 // run:()V
#33 = Class #46 // java/lang/System
#34 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#35 = Class #49 // java/util/Arrays
#36 = NameAndType #50:#51 // toString:([Ljava/lang/Object;)Ljava/lang/String;
#37 = Class #52 // java/io/PrintStream
#38 = NameAndType #53:#54 // println:(Ljava/lang/String;)V
#39 = Utf8 com/company/LambdaTest
#40 = Utf8 java/lang/Object
#41 = Methodref #55.#56 // 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;
#42 = Methodref #7.#57 // com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#43 = Utf8 run
#44 = Utf8 ([Ljava/lang/String;)Ljava/lang/Runnable;
#45 = Utf8 java/lang/Runnable
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 java/util/Arrays
#50 = Utf8 toString
#51 = Utf8 ([Ljava/lang/Object;)Ljava/lang/String;
#52 = Utf8 java/io/PrintStream
#53 = Utf8 println
#54 = Utf8 (Ljava/lang/String;)V
#55 = Class #58 // java/lang/invoke/LambdaMetafactory
#56 = NameAndType #59:#63 // 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;
#57 = NameAndType #22:#17 // lambda$main$0:([Ljava/lang/String;)V
#58 = Utf8 java/lang/invoke/LambdaMetafactory
#59 = Utf8 metafactory
#60 = Class #65 // java/lang/invoke/MethodHandles$Lookup
#61 = Utf8 Lookup
#62 = Utf8 InnerClasses
#63 = 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;
#64 = Class #66 // java/lang/invoke/MethodHandles
#65 = Utf8 java/lang/invoke/MethodHandles$Lookup
#66 = Utf8 java/lang/invoke/MethodHandles
{
public com.company.LambdaTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/company/LambdaTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: invokedynamic #2, 0 // InvokeDynamic #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
6: astore_1
7: aload_1
8: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V
13: return
LineNumberTable:
line 8: 0
line 9: 7
line 10: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 args [Ljava/lang/String;
7 7 1 r Ljava/lang/Runnable;
private static void lambda$main$0(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokestatic #5 // Method java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
7: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
}
SourceFile: "LambdaTest.java"
InnerClasses:
public static final #61= #60 of #64; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 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:
#28 ()V
#29 invokestatic com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#28 ()V
可以发现多出了一个新方法,方法体就是 lambda 体(lambda body),转换为源码如下:
private static void lambda$main$0(java.lang.String[] args){
System.out.println(Arrays.toString(args));
}
主要看一下 main 方法,并没有直接调用上面的方法,而是出现一条 inDy 指令:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: invokedynamic #2, 0 // InvokeDynamic #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
6: astore_1
7: aload_1
8: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V
13: return
可以看到 inDy 指向一个类型为 CONSTANT_InvokeDynamic_info 的常量项 #2
,另外 0
是预留参数,暂时没有作用。
#2 = InvokeDynamic #0:#30 // #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
#0
表示在 Bootstrap methods 表中的索引:
BootstrapMethods:
0: #27 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:
#28 ()V
#29 invokestatic com/company/LambdaTest.lambda$main$0:([Ljava/lang/String;)V
#28 ()V
#30
则是一个 CONSTANT_NameAndType_info,表示方法名和方法类型(返回值和参数列表),这个会作为参数传递给 BSM。
#30 = NameAndType #43:#44 // run:([Ljava/lang/String;)Ljava/lang/Runnable;
再看回表中的第 0 项,#27
是一个 [CONSTANT_MethodHandle_info,实际上是个 MethodHandle(方法句柄)对象,这个句柄指向的就是 BSM 方法。在这里就是:
java.lang.invoke.LambdaMetafactory.metafactory(MethodHandles.Lookup,String,MethodType,MethodType,MethodHandle,MethodType)
BSM 前三个参数是固定的,后面还可以附加任意数量的参数,但是参数的类型是有限制的,参数类型只能是
String
Class
int
long
float
double
MethodHandle
MethodType
LambdaMetafactory.metafactory 带多三个参数
Method arguments:
#25 ()V
#26 invokestatic com/company/LambdaTest.lambda$main$0:()V
#25 ()V
inDy 所需要的数据大概就是这些.
inDy 运行时
每一个 inDy 指令都称为 Dynamic Call Site(动态调用点),根据 jvm 规范所说的,inDy 可以分为两步,这两步部分代码代码是在 java 层的,给 metafactory 方法设断点可以看到一些行为。
第一步 inDy 需要一个 CallSite(调用点对象),CallSite 是由 BSM 返回的,所以这一步就是调用 BSM 方法。
调用 BSM 方法可以看作 invokevirtual 指令执行一个 invoke 方法,方法签名如下:
invoke:(MethodHandle,Lookup,String,MethodType,/*其他附加静态参数*/)CallSite
前四个参数是固定的,被依次压入操作栈里
MethodHandle,实际上这个方法句柄就是指向 BSM
Lookup, 也就是调用者,是 Indy 指令所在的类的上下文,可以通过 Lookup#lookupClass()获取这个类
name ,lambda 所实现的方法名,也就是"run"
invokedType,调用点的方法签名,这里是 methodType(Runnable.class,String[].class)
接下来就是执行 LambdaMetafactory.metafactory
方法了,它会创建一个匿名类,这个类是通过 ASM 编织字节码在内存中生成的,然后直接通过 unsafe 直接加载而不会写到文件里。不过可以通过下面的虚拟机参数让它运行的时候输出到文件
-Djdk.internal.lambda.dumpProxyClasses=
这个类是根据 lambda 的特点生成的,输出后可以看到,在这个例子中是这样的:
import java.lang.invoke.LambdaForm.Hidden;
// $FF: synthetic class
final class LambdaTest$$Lambda$1 implements Runnable {
private final String[] arg$1;
private LambdaTest$$Lambda$1(String[] var1) {
this.arg$1 = var1;
}
private static Runnable get$Lambda(String[] var0) {
return new LambdaTest$$Lambda$1(var0);
}
@Hidden
public void run() {
LambdaTest.lambda$main$0(this.arg$1);
}
}
然后就是创建一个 CallSite,绑定一个 MethodHandle,指向的方法其实就是生成的类中的静态方法 LambdaTest$$LambdaLambda(String[])Runnable。然后把调用点对象返回,到这里 BSM 方法执行完毕。
更详细的可参考:
- 浅谈Lambda Expression -
- [Java] 关于OpenJDK对Java 8 lambda表达式的运行时实现的查看方式 - 知乎专栏
第二步,就是执行这个方法句柄了,这个过程就像 invokevirtual 指令执行 MethodHandle#invokeExact 一样,
加上 inDy 上面那一条 aload_0 指令,这是操作数栈有两个分别是:
- args[],lambda 里面调用了 main 方法的参数
- 调用点对象(CallSite),实际上是方法句柄。如果是 CostantCallSite 的时候,inDy 会直接跟他的方法句柄链接。
传入 args,执行方法,返回一个 Runnable 对象,压入栈顶。到这里 inDy 就执行完毕。
接下来的指令就很好理解,astore_1 把栈顶的 Runnable 对象放到局部变量表的槽位1,也是变量 r。剩下的就是再拿出来调用 run 方法。