lambda表达式
wiki
- 通过字节码分析JDK8中Lambda表达式编译及执行机制
- Java 8 Lambda 揭秘
- Java8学习笔记(4) -- Lambda表达式实现方式
- 关于OpenJDK对Java 8 lambda表达式的运行时实现的查看方式
- 理解 invokedynamic
- Invokedynamic:Java的秘密武器
学习目标
- 代码层面理解怎么使用lambda表达式;
- 编译层面理解lambda的机制;
- 理解invokedynamic指令的机制;
- bootstrap方法的参数具体是什么,以及怎么调试得到参数;
- 疑问
- metafactory方法的参数具体表示什么?
- 字节码中关于boostraps方法的部分怎么理解?
How:实现原理
使用代码
package CompilerTestPackage;
import java.util.function.Consumer;
public class LambdaTest {
public static void main(String[] args)
{
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.getName());
greeter.accept(new Person("Lambda"));
}
}
class Person{
public String name;
public Person(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
}
通过cfr反编译代码
-
命令
java -jar .\cfr_0_132.jar .\LambdaTest.class --decodelambdas false > D:\LambdTestCfr.txt
package CompilerTestPackage;
import CompilerTestPackage.Person;
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.function.Consumer;
public class LambdaTest {
public static void main(String[] args) {
Consumer<Person> greeter = (Consumer<Person>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(CompilerTestPackage.Person ), (LCompilerTestPackage/Person;)V)();
greeter.accept(new Person("Lambda"));
}
private static /* synthetic */ void lambda$main$0(Person p) {
System.out.println("Hello, " + p.getName());
}
}
- Lambda表达式生成了一个lambda0()方法;
- 调用LambdaMetafactory.metafactory()方法
字节码层面
Classfile /D:/test/LambdaTest.class
Last modified 2018-8-5; size 1703 bytes
MD5 checksum f7703a9885aa7896abfb5a773b708be2
Compiled from "LambdaTest.java"
public class CompilerTestPackage.LambdaTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #16.#38 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0:#44 // #0:accept:()Ljava/util/function/Consumer;
#3 = Class #45 // CompilerTestPackage/Person
#4 = String #46 // Lambda
#5 = Methodref #3.#47 // CompilerTestPackage/Person."<init>":(Ljava/lang/String;)V
#6 = InterfaceMethodref #48.#49 // java/util/function/Consumer.accept:(Ljava/lang/Object;)V
#7 = Fieldref #50.#51 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #52 // java/lang/StringBuilder
#9 = Methodref #8.#38 // java/lang/StringBuilder."<init>":()V
#10 = String #53 // Hello,
#11 = Methodref #8.#54 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#12 = Methodref #3.#55 // CompilerTestPackage/Person.getName:()Ljava/lang/String;
#13 = Methodref #8.#56 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#14 = Methodref #57.#58 // java/io/PrintStream.println:(Ljava/lang/String;)V
#15 = Class #59 // CompilerTestPackage/LambdaTest
#16 = Class #60 // java/lang/Object
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 LocalVariableTable
#22 = Utf8 this
#23 = Utf8 LCompilerTestPackage/LambdaTest;
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 args
#27 = Utf8 [Ljava/lang/String;
#28 = Utf8 greeter
#29 = Utf8 Ljava/util/function/Consumer;
#30 = Utf8 LocalVariableTypeTable
#31 = Utf8 Ljava/util/function/Consumer<LCompilerTestPackage/Person;>;
#32 = Utf8 lambda$main$0
#33 = Utf8 (LCompilerTestPackage/Person;)V
#34 = Utf8 p
#35 = Utf8 LCompilerTestPackage/Person;
#36 = Utf8 SourceFile
#37 = Utf8 LambdaTest.java
#38 = NameAndType #17:#18 // "<init>":()V
#39 = Utf8 BootstrapMethods
#40 = MethodHandle #6:#61 // 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;
#41 = MethodType #62 // (Ljava/lang/Object;)V
#42 = MethodHandle #6:#63 // invokestatic CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
#43 = MethodType #33 // (LCompilerTestPackage/Person;)V
#44 = NameAndType #64:#65 // accept:()Ljava/util/function/Consumer;
#45 = Utf8 CompilerTestPackage/Person
#46 = Utf8 Lambda
#47 = NameAndType #17:#66 // "<init>":(Ljava/lang/String;)V
#48 = Class #67 // java/util/function/Consumer
#49 = NameAndType #64:#62 // accept:(Ljava/lang/Object;)V
#50 = Class #68 // java/lang/System
#51 = NameAndType #69:#70 // out:Ljava/io/PrintStream;
#52 = Utf8 java/lang/StringBuilder
#53 = Utf8 Hello,
#54 = NameAndType #71:#72 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#55 = NameAndType #73:#74 // getName:()Ljava/lang/String;
#56 = NameAndType #75:#74 // toString:()Ljava/lang/String;
#57 = Class #76 // java/io/PrintStream
#58 = NameAndType #77:#66 // println:(Ljava/lang/String;)V
#59 = Utf8 CompilerTestPackage/LambdaTest
#60 = Utf8 java/lang/Object
#61 = Methodref #78.#79 // 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;
#62 = Utf8 (Ljava/lang/Object;)V
#63 = Methodref #15.#80 // CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
#64 = Utf8 accept
#65 = Utf8 ()Ljava/util/function/Consumer;
#66 = Utf8 (Ljava/lang/String;)V
#67 = Utf8 java/util/function/Consumer
#68 = Utf8 java/lang/System
#69 = Utf8 out
#70 = Utf8 Ljava/io/PrintStream;
#71 = Utf8 append
#72 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#73 = Utf8 getName
#74 = Utf8 ()Ljava/lang/String;
#75 = Utf8 toString
#76 = Utf8 java/io/PrintStream
#77 = Utf8 println
#78 = Class #81 // java/lang/invoke/LambdaMetafactory
#79 = NameAndType #82:#86 // 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;
#80 = NameAndType #32:#33 // lambda$main$0:(LCompilerTestPackage/Person;)V
#81 = Utf8 java/lang/invoke/LambdaMetafactory
#82 = Utf8 metafactory
#83 = Class #88 // java/lang/invoke/MethodHandles$Lookup
#84 = Utf8 Lookup
#85 = Utf8 InnerClasses
#86 = 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;
#87 = Class #89 // java/lang/invoke/MethodHandles
#88 = Utf8 java/lang/invoke/MethodHandles$Lookup
#89 = Utf8 java/lang/invoke/MethodHandles
{
public CompilerTestPackage.LambdaTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LCompilerTestPackage/LambdaTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
5: astore_1
6: aload_1
7: new #3 // class CompilerTestPackage/Person
10: dup
11: ldc #4 // String Lambda
13: invokespecial #5 // Method CompilerTestPackage/Person."<init>":(Ljava/lang/String;)V
16: invokeinterface #6, 2 // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
21: return
LineNumberTable:
line 8: 0
line 9: 6
line 10: 21
LocalVariableTable:
Start Length Slot Name Signature
0 22 0 args [Ljava/lang/String;
6 16 1 greeter Ljava/util/function/Consumer;
LocalVariableTypeTable:
Start Length Slot Name Signature
6 16 1 greeter Ljava/util/function/Consumer<LCompilerTestPackage/Person;>;
}
SourceFile: "LambdaTest.java"
InnerClasses:
public static final #84= #83 of #87; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #40 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:
#41 (Ljava/lang/Object;)V
#42 invokestatic CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
#43 (LCompilerTestPackage/Person;)V
- 从字节码中可以看出,lambda表达式是通过invokedynamic实现的;
lambda表达式的invokedynamic指令
-
invokedynamic指令有四个操作数,格式为
invokedynamic indexbyte1 indexbyte2 0 0
- 前两个操作数构成一个索引,指向常量池,关联CONSTANT_InvokeDynamic_info结构;
- 后两个操作数保留,必须为0;
-
CONSTANT_InvokeDynamic_info 结构定义
CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; // 指向bootstrap_methods的一个有效索引值,其结构在属性表的 bootstrap method 结构中 u2 name_and_type_index; }
-
BootstrapMethods属性结构
BootstrapMethods_attribute { u2 attribute_name_index; u4 attribute_length; u2 num_bootstrap_methods; { u2 bootstrap_method_ref; u2 num_bootstrap_arguments; u2 bootstrap_arguments[num_bootstrap_arguments]; } bootstrap_methods[num_bootstrap_methods]; }
指令调用图
lambda的机制总结
编译时
- lambda表达式生产一个方法,方法实现了表达式的代码逻辑;
- 编译生成invokedynamic指令,调用bootstrap方法,由java.lang.invoke.LambdaMetafactory.metafactory()方法实现;
运行时
- invokedynamic指令调用metafactory方法,返回一个callsite,此callsite返回目标类型的一个匿名实现类(MethodHandles.Lookup caller 的内部类),此类关联编译时产生的方法;
- lambda表达式调用时会调用匿名实现类关联的方法。
LambdaMetafactory.metafactory()方法
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
metafactory方法的参数
- caller: 由JVM提供的lookup context
- invokedName: JVM提供的NameAndType
- invokedType: JVM提供的期望的CallSite类型
- samMethodType: 函数式接口定义的方法的签名
- implMethod: 编译时产生的那个实现方法
- instantiatedMethodType: 强制的方法签名和返回类型, 一般和samMethodType相同或者是它的一个特例
查看匿名实现类
IDEA配置JVM参数
- Run-->Edit Configurations
- 在VM Options内输入-Djdk.internal.lambda.dumpProxyClasses=<path_to_your_dump_directory>
- 匿名类查看
package CompilerTestPackage;
import CompilerTestPackage.LambdaTest;
import CompilerTestPackage.Person;
import java.lang.invoke.LambdaForm;
import java.util.function.Consumer;
final class LambdaTest$$Lambda$1
implements Consumer {
private LambdaTest$$Lambda$1() {
}
@LambdaForm.Hidden
public void accept(Object object) {
LambdaTest.lambda$main$0((Person)((Person)object));// 导入lambda$main$0方法
}
}
invokedynamic指令学习
-
理念
要让invokedynamic正常运行,一个核心的概念就是方法句柄(method handle)。它代表了一个可以从invokedynamic调用点进行调用的方法。这里的基本理念就是每个invokedynamic指令都会与一个特定的方法关联(也就是引导方法或BSM)。当解释器(interpreter)遇到invokedynamic指令的时候,BSM会被调用。它会返回一个对象(包含了一个方法句柄),这个对象表明了调用点要实际执行哪个方法。
-
一个Java方法可以视为由四个基本内容所构成:
- 名称
- 签名(包含返回类型)
- 定义它的类
- 实现方法的字节码