java8 lambda學習筆記之編譯與運行過程

從最簡單的lambda說起LambdaTest.java,代碼如下:

package lambda.demo;

import java.util.function.Supplier;

public class LambdaTest {
    public static void main(String[] args) {
        Supplier<Integer> supplier = () -> 1;
        System.out.println(supplier.get());
    }
}
構建了一個lambda表達式賦值給Supplier函數接口,然後打印Supplier對象提供的值。

編譯這個類,並用javap查看生成的字節碼,部分字節碼如下:

public class lambda.demo.LambdaTest
  SourceFile: "LambdaTest.java"
  InnerClasses:
       public static final #66= #65 of #69; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
  BootstrapMethods:
    0: #30 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:
        #31 ()Ljava/lang/Object;
        #32 invokestatic lambda/demo/LambdaTest.lambda$main$0:()Ljava/lang/Integer;
        #33 ()Ljava/lang/Integer;
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#28         //  java/lang/Object."<init>":()V
   #2 = InvokeDynamic      #0:#34         //  #0:get:()Ljava/util/function/Supplier;
  .......
  #27 = Utf8               LambdaTest.java
  #28 = NameAndType        #9:#10         //  "<init>":()V
  #29 = Utf8               BootstrapMethods
  #30 = MethodHandle       #6:#45         //  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;
  #31 = MethodType         #46            //  ()Ljava/lang/Object;
  #32 = MethodHandle       #6:#47         //  invokestatic lambda/demo/LambdaTest.lambda$main$0:()Ljava/lang/Integer;
  #33 = MethodType         #25            //  ()Ljava/lang/Integer;
  ......
{
  ......
  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:get:()Ljava/util/function/Supplier;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: invokeinterface #4,  1            // InterfaceMethod java/util/function/Supplier.get:()Ljava/lang/Object;
        15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        18: return
  ......		
}
可以看到第32行出現了invokedynamic指令,這正是lambda表達式在編譯的時候生成的,順着常量池索引#2找到CONSTANT_InvokeDynamic_info,由兩部分組成:

(1)bootstrap方法限定符

每個invokedynamic指令出現的地方被稱爲動態調用點,每個動態調用點都對應一個bootstrap方法,bootstrap方法返回值爲CallSite,此例中bootstrap方法爲java.lang.invoke.LambdaMetafactory類的靜態方法metafactory。

在方法限定符中還有三個參數分別是:

#31 ()Ljava/lang/Object; 表示函數式接口Supplier.get方法描述符
#32 invokestatic lambda/demo/LambdaTest.lambda$main$0:()Ljava/lang/Integer; lambda表達式編譯時生成的匿名靜態方法對應的描述符,這個匿名方法具體情況如下:

所在類:lambda.demo.LambdaTest

方法名:lambda$main$0

參數:無

返回值:java.lang.Integer
#33 ()Ljava/lang/Integer;具體的方法描述符,與#31相對應

(2)方法名和方法描述符:此處爲Supplier get().


在這裏可以看出:lambda在編譯的時候生成匿名靜態方法,同時還生成了invokedynamic指令,其bootstrap方法對應的動態調用點的MethodHandle,底層方法就是這個匿名方法。


當我們運行main函數時,JVM會將這些參數出棧,並傳入LambdaMetafactory.metafactory方法中,以下是metafactory方法的定義:

public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
此處caller爲lambda.demo.LambdaTest類

invokedName爲get

invokedType爲()Supplier

後面三個參數分別爲方法限定符中的三個靜態參數。

真正創建動態調用點的地方在InnerClassLambdaMetafactory.buildCallSite方法中,主要做了三件事:

(1)通過ASM工具生成名爲lambda.demo.LambdaTest$$Lambda$1的類的字節碼;注意ForwardingMethodGenerator.generate方法中以下代碼:

visitMethodInsn(invocationOpcode(), implMethodClassName,
                            implMethodName, implMethodDesc,
                            implDefiningClass.isInterface());
是將編譯時生成的匿名方法對應的字節碼加入到新生成的類中

(2)爲lambda.demo.LambdaTest$$Lambda$1類創建實例,並轉換爲Supplier對應的MethodHandle;

(3)將(2)中的MethodHandler做爲target生成動態調用點,並返回。


在這裏可以看出:運行時通過ASM工具生成新類的字節碼,包含了編譯時的匿名方法,新類最終會被包裝成一個動態調用點。執行invokedynamic指令,就會執行動態調用點的bootstrap方法,其底層方法就是編譯時的匿名方法,也就是執行lambda表達式中的代碼。


新生成的類的命名規則爲:包名.類名$$Lambda$自增計數,當我們在debug或者拋出異常時看到這種類名就可以理解了:

Exception in thread "main" java.lang.RuntimeException
	at lambda.demo.LambdaTest.lambda$main$0(LambdaTest.java:13)
	at lambda.demo.LambdaTest$$Lambda$1/1283928880.accept(Unknown Source)
	at lambda.demo.LambdaTest.main(LambdaTest.java:15)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章