Show the implementation of Java8 Lambda via byte code

Posted by sgt.wolfgang on Sun, 28 Jul 2019 18:40:25 +0200

Java8 adds Lambda expressions, which greatly simplifies and compacts the code. How does Java8 implement Lambda expressions?

Take a look directly at a simple example of creating threads.

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

Execute the javac TestLambda.java compilation to generate the file TestLambda.class, and then analyze the class file with the javap command.
Execute javap-p TestLambda to display all classes and members.

// 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(); // Compiled
}

From the code above, you can see that the compiler generates a private static method based on the Lambda expression.

private static void lambda$main$0(); 

Print the details using the command 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

As shown above: The decompiled byte code for a line of code that the main() method creates a thread is

 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

Find the third row of the constant pool based on 4: invokedynamic #3

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

Where #0 points to the static factory method LambdaMetafactory.metafactory for BootstrapMethods:

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 uses asm to generate internal classes for Lambda expressions. The source code for the Metafactory method is as follows

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();
}

To view this internal class, run TestLambda with the parameters to generate the proxy class, which is java-Djdk.internal.lambda.dumpProxyClasses TestLambda.After running, it was found that a class file TestLambda$$Lambda$1.class was generated in the same directory.Execute javap-v-p TestLambda$$Lambda$1 to decompile this class.

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()
}

As you can see from above, the internal class TestLambda$$Lambda$1 implements java.lang.Runnable, the run() method, which is the method executed after the thread starts, and triggers the execution of TestLambda.lambda$main$0:(), which is the private static method generated when TestLambda is compiled.

summary

A Lambda expression that creates a thread adds a private static method to the class file after compilation. During running this class, an internal class is dynamically generated using the asm-operated bytecode technology. This internal class implements the Runnable interface and executes the thread. The internal thread method run actually calls the compilationPrivate static methods generated after translation to execute thread code.

Topics: PHP Java Lambda