1.前言html
2014年十月份的時候Debug了下Lambda的實現代碼, 大概瞭解了Lambda的實現, 昨天回憶了下, 發現以忘光, 仍是寫篇博客吧, 方便記憶java
這篇文章是我本地Debug後記錄下來的所見所聞, 不必定徹底正確, 若有錯誤, 請務必指出.api
2.環境oracle
JDK: Oracle JDK1.8.0_05 64位 , Eclipse4.4 app
3.過程jvm
初看Lambda時覺得Lambda就是編譯器幫咱們把Lambda表達式給編譯成了一個匿名內部類, 而後調用, 可是偶然間看到字節碼文件後, 發現有點不一樣, 因而研究了下.ide
//源碼是這樣的 public static void main(String[] args) throws Throwable { String hello = "hello lambda "; Function<String, Void> func = (name) -> { System.out.println(hello + name); return null; }; func.apply("haogrgr"); } //本來覺得編譯器會將Lambda表達式編譯成這樣. String hello = "hello lambda "; Function<String, Void> func = new Function<String, Void>() { @Override public Void apply(String name) { System.out.println(hello + name); return null; } } func.apply("haogrgr");
//可是發現字節碼是這樣的 //main方法塊 public static void main(java.lang.String[] args) throws java.lang.Throwable; 0 ldc <String "hello lambda "> [19] 2 astore_1 [hello] 3 aload_1 [hello] 4 invokedynamic 0 apply(java.lang.String) : java.util.function.Function [24] 9 astore_2 [func] 10 aload_2 [func] 11 ldc <String "haogrgr"> [25] 13 invokeinterface java.util.function.Function.apply(java.lang.Object) : java.lang.Object [27] [nargs: 2] 18 pop 19 return //Lambda表達式的內容被編譯器編譯成了當前類的一個static方法 命名爲lambda$0, 將使用到的外部變量用參數替代. //synthetic 標記表示這個方法是否由編譯器產生.(字段或方法訪問標誌ACC_SYNTHETIC) private static synthetic java.lang.Void lambda$0(java.lang.String arg0, java.lang.String name); 0 getstatic java.lang.System.out : java.io.PrintStream [42] 3 new java.lang.StringBuilder [48] 6 dup 7 aload_0 [arg0] 8 invokestatic java.lang.String.valueOf(java.lang.Object) : java.lang.String [50] 11 invokespecial java.lang.StringBuilder(java.lang.String) [56] 14 aload_1 [name] 15 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [59] 18 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [63] 21 invokevirtual java.io.PrintStream.println(java.lang.String) : void [67] 24 aconst_null 25 areturn //這裏是invokedynamic指令的引導方法 Bootstrap methods: 0 : # 82 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: #83 (Ljava/lang/Object;)Ljava/lang/Object; #86 invokestatic com/haogrgr/java8/main/Main.lambda$0:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Void; #88 (Ljava/lang/String;)Ljava/lang/Void;
先看看invokedynamic 0 apply(java.lang.String) : java.util.function.Function [24] 這個指令 (介紹能夠參考周志明的 <深刻理解Java虛擬機 第二版> 263頁)函數
這個指令形式爲 invokedynamic indexbyte1, indexbyte2, 0, 0 參考維基百科連接: http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings 優化
這條指令在這裏對應的二進制 BA 00 18 00 00 , BA表示invokedynamic指令對應的十六進制, 十六進制(0018) = 十進制(24), 後面2個字節暫時沒有用到. ui
indexbyte1 和 indexbyte2 這2個字節表示常量池索引, 也就是上面的 [24], 該常量池索引指向了一個CONSTANT_InvokeDynamic條目。
這個條目索引了引導方法(BootstrapMethods) 和 動態調用點相關的方法名字及方法類型(CONSTANT_NameAndType_info)。
在這裏, 引導方法爲LambdaMetafactory.metafactory, 方法的名字爲 apply, 方法的類型爲 : (Ljava/lang/String;)Ljava/util/function/Function;
(表示有一個String類型的參數, 方法返回值類型爲java.util.function.Function)
再看看lambda$0的字節碼, 能夠看到, 就是Lambda表達式體的代碼, 大概以下代碼, 能夠看到, 用到的局部變量hello被參數arg0替換了.
private static synthetic Void lambda$0(String arg0, String name){ System.out.println(arg0 + name); return null; }
編譯器會將Lambda表達式的內容, 編譯成當前類的一個實例或靜態方法(取決於Lambda表達式出如今實例方法中仍是靜態方法中)
最後看看Bootstrap方法
介紹能夠參考這裏: http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html
http://han.guokai.blog.163.com/blog/static/13671827120118125237946 (網上找到的上面的翻譯)
做用:
每個invokedynamic指令的實例叫作一個動態調用點(dynamic call site), 動態調用點最開始是未連接狀態(unlinked:表示還未指定該調用點要調用的方法),
動態調用點依靠引導方法來連接到具體的方法. 引導方法是由編譯器生成, 在運行期當JVM第一次遇到invokedynamic指令時, 會調用引導方法來
將invokedynamic指令所指定的名字(方法名,方法簽名)和具體的執行代碼(目標方法)連接起來, 引導方法的返回值永久的決定了調用點的行爲.
引導方法的返回值類型是java.lang.invoke.CallSite, 一個invokedynamic指令關聯一個CallSite, 將全部的調用委託到CallSite當前的target(MethodHandle)
參數:(說明來自api)
LambdaMetafactory.metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)有六個參數, 按順序描述以下
1. MethodHandles.Lookup caller : 表明查找上下文與調用者的訪問權限, 使用invokedynamic指令時, JVM會自動自動填充這個參數, 這裏JVM爲咱們填充
爲Lookup(com.haogrgr.java8.main.Main.class, (PUBLIC | PRIVATE | PROTECTED | PACKAGE)) 意思是這個Lookup實例能夠訪問Main類的全部成員.
2. String invokedName : 要實現的方法的名字, 使用invokedynamic時, JVM自動幫咱們填充(填充內容來自常量池InvokeDynamic.NameAndType.Name),
在這裏JVM爲咱們填充爲 "apply", 即Function.apply方法名.
3. MethodType invokedType : 調用點指望的方法參數的類型和返回值的類型(方法signature). 使用invokedynamic指令時, JVM會自動自動填充這個參數
(填充內容來自常量池InvokeDynamic.NameAndType.Type), 在這裏參數爲String, 返回值類型爲Function, 表示這個調用點的目標方法的參數爲String,
而後invokedynamic執行完後會返回一個Function實例 ((String)Function).
4. MethodType samMethodType : 函數對象將要實現的接口方法類型, 這裏運行時, 值爲 (Object)Object 即 Function.apply方法的類型(泛型信息被擦除).
5. MethodHandle implMethod : 一個直接方法句柄(DirectMethodHandle), 描述在調用時將被執行的具體實現方法 (包含適當的參數適配, 返回類型適配,
和在調用參數前附加上捕獲的參數), 在這裏爲 com.haogrgr.java8.main.Main.lambda$0(String,String)Void 方法的方法句柄.
6. MethodType instantiatedMethodType : 函數接口方法替換泛型爲具體類型後的方法類型, 一般和 samMethodType 同樣, 不一樣的狀況爲:
好比函數接口方法定義爲 T apply(R r) T和R都爲泛型標識, 這個時候方法類型爲(Object)Object, 在編譯時T和R都已肯定, 這個時候具體的方法類型可能
爲(String)Void 即T和R由具體的Void和String替換, 這時samMethodType就是 (Object)Object, 而instantiatedMethodType爲(String)Void.
第4, 5, 6 三個參數來自class文件中的. 如上面引導方法字節碼中Method arguments後面的三個參數就是將應用於4, 5, 6的參數.
Lambda表達式結果:
從這裏能夠看出, Lambda表達會返回一個對應接口的具體實現實例, 能夠看到這裏的Lambda表達式返回了一個 Function<String, Void> 的實例.
Function<String, Void> func = (name) -> { System.out.println(hello + name); return null; };
那麼, 前面說了, 這裏Lambda表達式的內容被編譯成了Main類的一個static方法, 那麼這個實例是怎麼回事呢? 先來看看這個實例的字節碼.
JDK提供了一個參數來輸出生動態生成的類的字節碼: System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
還一個好玩的屬性是-Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true, 這個只能經過vm參數來指定了.
將上面的屬性能夠經過啓動參數設置, 也能夠在代碼裏設置, 若是在代碼裏設置, 請放在main方法第一行, 下面是上面例子中生成的類的字節碼
final synthetic class com.haogrgr.java8.main.Main$$Lambda$1 implements java.util.function.Function { // Field descriptor #8 Ljava/lang/String; private final java.lang.String arg$1; // Method descriptor #10 (Ljava/lang/String;)V // Stack: 2, Locals: 2 private Main$$Lambda$1(java.lang.String arg0); 0 aload_0 [this] 1 invokespecial java.lang.Object() [13] 4 aload_0 [this] 5 aload_1 [arg0] 6 putfield com.haogrgr.java8.main.Main$$Lambda$1.arg$1 : java.lang.String [15] 9 return // Method descriptor #17 (Ljava/lang/String;)Ljava/util/function/Function; // Stack: 3, Locals: 1 private static java.util.function.Function get$Lambda(java.lang.String arg0); 0 new com.haogrgr.java8.main.Main$$Lambda$1 [2] 3 dup 4 aload_0 [arg0] 5 invokespecial com.haogrgr.java8.main.Main$$Lambda$1(java.lang.String) [19] 8 areturn // Method descriptor #21 (Ljava/lang/Object;)Ljava/lang/Object; // Stack: 2, Locals: 2 public java.lang.Object apply(java.lang.Object arg0); 0 aload_0 [this] 1 getfield com.haogrgr.java8.main.Main$$Lambda$1.arg$1 : java.lang.String [15] 4 aload_1 [arg0] 5 checkcast java.lang.String [23] 8 invokestatic com.haogrgr.java8.main.Main.lambda$0(java.lang.String, java.lang.String) : java.lang.Void [29] 11 areturn }
下面是將上面的字節碼翻譯過來後的內容(Main.lambda$0()方法的反編譯代碼在前面有寫)
package com.haogrgr.java8.main; import java.util.function.Function; final class Main$$Lambda$1 implements Function<String, Void> { private final String hello; private Main$$Lambda$1(String hello){ this.hello = hello; } private static Function<String, Void> get$Lambda(String hello){ return new Main$$Lambda$1(hello); } @Override public Void apply(String name) { return Main.lambda$0(this.hello, name); } }
這裏能夠看到, 由於Lambda有用到hello這個局部變量, 因而將這個局部變量的值保存在了生成的實例中的一個final屬性(這是否是叫作捕獲()?),
這也就說明了, 爲何用於Lambda裏面的外部局部變量必須是final類型的或者不能從新賦值.
同時, 咱們看到, 實例實現了Function接口, 接口方法的實現爲調用Main中Lambda生成的靜態方法.
整理:
如今, 咱們知道了:
1.Lambda表達的內容被編譯成了當前類的一個靜態或實例方法.
2.Lambda表達式所在處會產生一條invokedynamic指令調用, 同時編譯器會生成一個對應的Bootstrap Method.
3.當JVM第一次碰到這條invokedynamic時, 會調用對應的Bootstrap方法.
4.由Lambda表達式產生的invokedynamic指令的引導方法是調用LambdaMetafactory.metafactory()方法.
5.調用引導方法會返回一個CallSite對象實例, 該實例target引用一個MethodHandle實例.
6.執行MethodHanlde表明的方法(?), 返回結果, 結果爲動態生成的接口實例, 接口實現調用1布中生成的方法.
調試思路:
斷點LambdaMetafactory.metafactory()方法.
後續邏輯: 1.OSC博客字數限制, 今天先寫到這裏, 後面的下個星期再寫, 後面大概內容, 我好人, 不留懸念~~~~
1. LambdaMetafactory.metafactory()方法邏輯主要是生成動態代理類Class字節碼 和 建立CallSite,具體是ConstantCallSite子類.
2. ConstantCallSite類的target引用的MH根據狀況可能爲BoundMethodHandle.Species_L(當Lambda沒有用到外部變量時, 一種優化)
或者 DirectMethodHandle(上面的例子就是這種, 若是沒有用到hello變量時, 就會是Species_L類).
3. 2 中的MH的語義爲(之前面的代碼爲例): 調用 Main$$Lambda$1.get$Lambda(String hello)方法來構造 1 中生成的動態類實例(略了點東西), 返回.
若是是Species_L, 則, Species_L有個屬性argL0, 存放的是動態類的實例(調用引導方法時就已實例化), 而後它的語義就是 : 獲取本身的argL0屬性, 而後返回.
4. LambdaForm : The symbolic, non-executable form of a method handle's invocation semantics.
6. 動態生成字節碼和匿名內部類兩種Lambda實現方式對比. (http://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf)
7. OpenJDK上關於Lambda實現的一篇文章, 裏面有介紹Lambda表達式編譯期的一些處理, 包含序列化兼容等等
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
8. 選擇使用IDY指令是爲了之後能夠方便的替換爲其餘實現.
9. 還會有一些好比自動裝箱, 參數轉換等等的高級特性, 我也沒太弄明白~~~
這篇文章是我本地Debug後記錄下來的所見所聞, 不必定徹底正確, 若有錯誤, 請務必指出.