java拉姆达表达式原理_Java8 Lambda表达式原理扫盲

背景

在使用Lamdba表达式,一直以为是内部类的方式实现的,但是一想如果每次调用都实例化一个内部类,性能肯定不好,难道Java里的lambda表达式真的是这么实现的吗?也许是该研究下原理了。

正文

测试代码:

public class Test{

public void test() {

Runnable r = () -> System.out.println(123);

r.run();

}

}

执行编译命令javac -g Test.java,得到class文件。

查看字节码

查看字节码javap -p -verbose Test得到:

Classfile /Users/liushijie/learn/Test.class

Last modified Nov 20, 2018; size 1058 bytes

MD5 checksum febbe61fdc1f4564d2e039067752d6fc

Compiled from "Test.java"

public class Test

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #7.#21 // java/lang/Object."":()V

#2 = InvokeDynamic #0:#26 // #0:run:()Ljava/lang/Runnable;

#3 = InterfaceMethodref #27.#28 // java/lang/Runnable.run:()V

#4 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream;

#5 = Methodref #31.#32 // java/io/PrintStream.println:(I)V

#6 = Class #33 // Test

#7 = Class #34 // java/lang/Object

#8 = Utf8

#9 = Utf8 ()V

#10 = Utf8 Code

#11 = Utf8 LineNumberTable

#12 = Utf8 LocalVariableTable

#13 = Utf8 this

#14 = Utf8 LTest;

#15 = Utf8 test

#16 = Utf8 r

#17 = Utf8 Ljava/lang/Runnable;

#18 = Utf8 lambda$test$0

#19 = Utf8 SourceFile

#20 = Utf8 Test.java

#21 = NameAndType #8:#9 // "":()V

#22 = Utf8 BootstrapMethods

#23 = MethodHandle #6:#35 // 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;

#24 = MethodType #9 // ()V

#25 = MethodHandle #6:#36 // invokestatic Test.lambda$test$0:()V

#26 = NameAndType #37:#38 // run:()Ljava/lang/Runnable;

#27 = Class #39 // java/lang/Runnable

#28 = NameAndType #37:#9 // run:()V

#29 = Class #40 // java/lang/System

#30 = NameAndType #41:#42 // out:Ljava/io/PrintStream;

#31 = Class #43 // java/io/PrintStream

#32 = NameAndType #44:#45 // println:(I)V

#33 = Utf8 Test

#34 = Utf8 java/lang/Object

#35 = Methodref #46.#47 // 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;

#36 = Methodref #6.#48 // Test.lambda$test$0:()V

#37 = Utf8 run

#38 = Utf8 ()Ljava/lang/Runnable;

#39 = Utf8 java/lang/Runnable

#40 = Utf8 java/lang/System

#41 = Utf8 out

#42 = Utf8 Ljava/io/PrintStream;

#43 = Utf8 java/io/PrintStream

#44 = Utf8 println

#45 = Utf8 (I)V

#46 = Class #49 // java/lang/invoke/LambdaMetafactory

#47 = NameAndType #50:#54 // 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;

#48 = NameAndType #18:#9 // lambda$test$0:()V

#49 = Utf8 java/lang/invoke/LambdaMetafactory

#50 = Utf8 metafactory

#51 = Class #56 // java/lang/invoke/MethodHandles$Lookup

#52 = Utf8 Lookup

#53 = Utf8 InnerClasses

#54 = 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;

#55 = Class #57 // java/lang/invoke/MethodHandles

#56 = Utf8 java/lang/invoke/MethodHandles$Lookup

#57 = Utf8 java/lang/invoke/MethodHandles

{

public Test();

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 1: 0

LocalVariableTable:

Start Length Slot Name Signature

0 5 0 this LTest;

public void test();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=2, args_size=1

0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;

5: astore_1

6: aload_1

7: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V

12: return

LineNumberTable:

line 3: 0

line 4: 6

line 5: 12

LocalVariableTable:

Start Length Slot Name Signature

0 13 0 this LTest;

6 7 1 r Ljava/lang/Runnable;

private static void lambda$test$0();

descriptor: ()V

flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC

Code:

stack=2, locals=0, args_size=0

0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;

3: bipush 123

5: invokevirtual #5 // Method java/io/PrintStream.println:(I)V

8: return

LineNumberTable:

line 3: 0

}

SourceFile: "Test.java"

InnerClasses:

public static final #52= #51 of #55; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

BootstrapMethods:

0: #23 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:

#24 ()V

#25 invokestatic Test.lambda$test$0:()V

#24 ()V

通过字节码文件我们可以看到,编译出来的字节码文件中新增一些玩意:

常量池里(#2)多了一个以前没有见过的InvokeDynamic指令

新增了一个静态私有方法:private static void lambda$test$0(),里面的内容正好是lambda表达式里的代码;

新增了一个BootstrapMethods属性,内部包含一个动态调用点列表,因为测试代码只有一个lambda表达式,所以我们只能看到一个调用点

在运行时有一个链接(link)过程,在JVM层面调用。通过链接操作,调用上面3中的调用点,调用点在动态生成实现了FunctionInterface接口的类,方法中则调用2中新增的lambda$test$0方法,生成类之后通过构造函数实例化一个对象,被调用点持有,调用点有一个字的常量池。在调用invokedynamic指令之前会发生链接过程。下文引自:参考5

里面也有提到过多线程场景,略过不提。

Before the JVM can execute a dynamic call site (an invokedynamic instruction), the call site must first be linked. Linking is accomplished by calling a bootstrap method which is given the static information content of the call site, and which must produce a method handle that gives the behavior of the call site.

总结

通过一些验证和资料检索,大概了解lambda的原理,是使用指令与动态生成的内部类来完成调用,而且正常只会被链接一次。从这个点上来看,对性能是没有什么损失的,可以放心的使用。

问题

自己梳理的比较肤浅,没有深挖最底层的实现。LambdaMetafactory.metafactory动态调用点的链接过程比较长,如果有动态调用的场景应该是可以参考的。翻到过一篇问答(见参考6),暂时没找到合适的场景使用,没有深入下去的动力。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值