通过字节码展示Java8 Lambda的实现

2019-08-09 00:00:00 lambda 字节 展示

Java8 增加了 Lambda 表达式,很大程度使代码变的更加简洁紧凑了,那么 Java8 是如何实现 Lambda 表达式的呢?

直接看一个简单的创建线程的例子。

public class TestLambda {
    public static void main(String[] args) {
        new Thread(() -> System.out.print("thread"));
    }
}

执行javac TestLambda.java编译生成文件TestLambda.class,然后用javap命令来分析这个class文件。
执行javap -p TestLambda显示所有类和成员。

// javap -p TestLambda
Compiled from "TestLambda.java"
public class TestLambda {
  public TestLambda();
  public static void main(java.lang.String[]);
  private static void lambda$main$0(); // 编译后生成的
}

由上面的代码可以看出编译器根据 Lambda 表达式生成了一个私有静态方法。

private static void lambda$main$0(); 

使用命令javap -v -p TestLambda打印详细信息。

public class TestLambda
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#24         // java/lang/Object."<init>":()V
   #2 = Class              #25            // java/lang/Thread
   #3 = InvokeDynamic      #0:#30         // #0:run:()Ljava/lang/Runnable;
   #4 = Methodref          #2.#31         // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
   #5 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = String             #34            // thread
   #7 = Methodref          #35.#36        // java/io/PrintStream.print:(Ljava/lang/String;)V
   #8 = Class              #37            // TestLambda
   #9 = Class              #38            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               LocalVariableTable
  #15 = Utf8               this
  #16 = Utf8               LTestLambda;
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               args
  #20 = Utf8               [Ljava/lang/String;
  #21 = Utf8               lambda$main$0
  #22 = Utf8               SourceFile
  #23 = Utf8               TestLambda.java
  #24 = NameAndType        #10:#11        // "<init>":()V
  #25 = Utf8               java/lang/Thread
  #26 = Utf8               BootstrapMethods
  #27 = MethodHandle       #6:#39         // 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;
  #28 = MethodType         #11            //  ()V
  #29 = MethodHandle       #6:#40         // invokestatic TestLambda.lambda$main$0:()V
  #30 = NameAndType        #41:#42        // run:()Ljava/lang/Runnable;
  #31 = NameAndType        #10:#43        // "<init>":(Ljava/lang/Runnable;)V
  #32 = Class              #44            // java/lang/System
  #33 = NameAndType        #45:#46        // out:Ljava/io/PrintStream;
  #34 = Utf8               thread
  #35 = Class              #47            // java/io/PrintStream
  #36 = NameAndType        #48:#49        // print:(Ljava/lang/String;)V
  #37 = Utf8               TestLambda
  #38 = Utf8               java/lang/Object
  #39 = Methodref          #50.#51        // 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;
  #40 = Methodref          #8.#52         // TestLambda.lambda$main$0:()V
  #41 = Utf8               run
  #42 = Utf8               ()Ljava/lang/Runnable;
  #43 = Utf8               (Ljava/lang/Runnable;)V
  #44 = Utf8               java/lang/System
  #45 = Utf8               out
  #46 = Utf8               Ljava/io/PrintStream;
  #47 = Utf8               java/io/PrintStream
  #48 = Utf8               print
  #49 = Utf8               (Ljava/lang/String;)V
  #50 = Class              #53            // java/lang/invoke/LambdaMetafactory
  #51 = NameAndType        #54:#58        // 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;
  #52 = NameAndType        #21:#11        // lambda$main$0:()V
  #53 = Utf8               java/lang/invoke/LambdaMetafactory
  #54 = Utf8               metafactory
  #55 = Class              #60            // java/lang/invoke/MethodHandles$Lookup
  #56 = Utf8               Lookup
  #57 = Utf8               InnerClasses
  #58 = 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;
  #59 = Class              #61            // java/lang/invoke/MethodHandles
  #60 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #61 = Utf8               java/lang/invoke/MethodHandles
{
  public TestLambda();
    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   LTestLambda;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #2                  // class java/lang/Thread
         3: dup
         4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
        12: pop
        13: return
      LineNumberTable:
        line 3: 0
        line 4: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;

  private static void lambda$main$0();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String thread
         5: invokevirtual #7                  // Method java/io/PrintStream.print:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
}
SourceFile: "TestLambda.java"
InnerClasses:
     public static final #56= #55 of #59; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #27 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:
      #28 ()V
      #29 invokestatic TestLambda.lambda$main$0:()V
      #28 ()V

如上所示:main() 方法创建线程的一行代码反编译后的字节码是

 0: new           #2                  // class java/lang/Thread
 3: dup
 4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
 9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: pop
13: return

根据4: invokedynamic #3找到常量池第三行

#3 = InvokeDynamic      #0:#30         // #0:run:()Ljava/lang/Runnable;

其中,#0指向BootstrapMethods:的静态工厂方法LambdaMetafactory.metafactory

BootstrapMethods:
  0: #27 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:
      #28 ()V
      #29 invokestatic TestLambda.lambda$main$0:()V
      #28 ()V

LambdaMetafactory.metafactory会利用 asm 可以为 Lambda 表达式生成内部类,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();
}

为了查看这个内部类,加上生成代理类的参数运行 TestLambda,即java -Djdk.internal.lambda.dumpProxyClasses TestLambda。运行结束后,发现在同级目录生成了一个class文件TestLambda$$Lambda$1.class。执行javap -v -p TestLambda$$Lambda$1反编译这个类。

final class TestLambda$$Lambda$1 implements java.lang.Runnable
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
   #1 = Utf8               TestLambda$$Lambda$1
   #2 = Class              #1             // TestLambda$$Lambda$1
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               java/lang/Runnable
   #6 = Class              #5             // java/lang/Runnable
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = NameAndType        #7:#8          // "<init>":()V
  #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
  #11 = Utf8               run
  #12 = Utf8               Ljava/lang/invoke/LambdaForm$Hidden;
  #13 = Utf8               TestLambda
  #14 = Class              #13            // TestLambda
  #15 = Utf8               lambda$main$0
  #16 = NameAndType        #15:#8         // lambda$main$0:()V
  #17 = Methodref          #14.#16        // TestLambda.lambda$main$0:()V
  #18 = Utf8               Code
  #19 = Utf8               RuntimeVisibleAnnotations
{
  private TestLambda$$Lambda$1();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: invokestatic  #17                 // Method TestLambda.lambda$main$0:()V
         3: return
    RuntimeVisibleAnnotations:
      0: #12()
}

由上可见,内部类TestLambda$$Lambda$1实现了 java.lang.Runnable,run()方法即线程启动后执行的方法,它会触发执行TestLambda.lambda$main$0:(),即编译TestLambda时生成的私有静态方法。

总结

创建线程的 Lambda 表达式在编译后会在 class 文件新增一个私有静态方法,运行这个类的期间,会使用 asm 操作字节码的技术,动态生成内部类,此内部类实现了 Runnable 接口,真正执行线程,线程方法 run 内部实际上就是调用了编译后生成的私有静态方法,从而执行线程代码。

相关文章