Java8 Lambda表示式原理掃盲
阿新 • • 發佈:2018-11-20
背景
在使用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."<init>":()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 <init> #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 // "<init>":()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."<init>":()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
裡面也有提到過多執行緒場景,略過不提。
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),暫時沒找到合適的場景使用,沒有深入下去的動力。
參考
- Lambda表示式實現方式
- InvokeDynamic指令JSR 292
- https://stackoverflow.com/questions/26775676/explicit-use-of-lambdametafactory
- https://zhuanlan.zhihu.com/p/27159693
- https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/package-summary.html#package.description
- https://stackoverflow.com/questions/26775676/explicit-use-of-lambdametafactory