Close

What Lambda Expressions are compiled to? What is their runtime behavior?

[Last Updated: Oct 2, 2018]

In prior versions of Java, we have been inlining custom code or custom functionality by wrapping it inside an anonymous class. Java 8 Lambda expression is a way to inline that functionality without creating anonymous class. Now we are able to pass plain method/functionality as arguments. In last topic, we saw how to write Lambda expressions. Here we are going discuss what they are compiled to and how they get executed during runtime.


No Anonymous Class generation during compile time

Lambda Expressions are not completely Syntactic Sugar, meaning compiler doesn't translate them into something which is already understood by JVM.

Similarly for lambda, a straight forward approach could be: just desugar the new syntax into anonymous classes during compile time. But conceptually that wouldn't make them true functions. Also anonymous classes have their own problems from day one, JVM has to perform rather expensive process of initiation a new class instance whenever it encounters an inline anonymous class. Unless the developer does some performance consideration while writing the code and initialize anonymous classes at instance level of enclosing class. But there is a lot of pain we have to go through to make our code efficient at the places where we could have just inline some functional piece of code. These are the reasons lambda designers didn't go for anonymous class approach. That also means by using lambda now, we can get rid of those anonymous class explosion in our output directory. For example you must have noticed that creating an anonymous class inside Foo.java results in something like Foo$1.class.

So what they are compiled to?

Lambda syntax written by the developer is desugared into JVM level instructions generated during compilation, meaning the actual responsibility of constructing lambda is deferred to runtime.


Compile Time Instructions and Dynamic construction

To make the Java less compile time strict, and to allow JVM languages to bind code at run-time, Java 7 introduced a new invokedynamic bytecode instruction to the JVM and a new java.lang.invoke API package (just a toolkit for compiler writer). invokedynamic facilitates the implementation of dynamic languages (for the JVM) through dynamic method invocation. That means now Java can add Functional programming constructs. Lambda engineers chose invokedynamic approach for lambdas.

What happens during compile time?

Instead of generating direct bytecode for lambda (like proposed anonymous class syntactic sugar approach), compiler declares a recipe (via invokeDynamic instructions) and delegates the real construction approach to runtime.

During compilation process following information is generated

  • The bootstrap method information. This is needed for JVM to construct object representation of lambda during runtime.
  • Lambda body code is generated within an instance or static desugared private method which has the same parameters and return type as the lambda's functional interface abstract method. The linkage to this method is done via invokespecial or invokestatic instructions by the compiler. Other than invokedyanmic, similar instructions are invokestatic, invokespecial, invokeinterface and invokevirtual, if interested, read more here
  • Target type information (the functional interface type e.g. Runnable or Consumer<T> etc) added to the recipe.

Here's a very simple class involving lambda, we are going to see what it's going to compile into:

public class Test {
    public static void main (String[] args) {
        Runnable r = () -> System.out.println();
    }
}

Now let's use javap tool on above class, notice the desugared method lambda$main$0:

D:\examples\lambdas>javac Test.java
D:\examples\lambdas>javap -p Test
Compiled from "Test.java"
public class Test {
  public Test();
  public static void main(java.lang.String[]);
  private static void lambda$main$0();
}

Let's see verbose output, we are omitting some stuff here. We don't have to understand everything here though

D:\examples\lambda>javap -verbose Test
Classfile /D:/projects/logicbig/src/main/java/Test.class
.....
  Compiled from "Test.java"
public class Test
.....
Constant pool:
   #1 = Methodref          #6.#16         // java/lang/Object."<init>":()V
   #2 = InvokeDynamic      #0:#21         // #0:run:()Ljava/lang/Runnable;
......
   #17 = Utf8               BootstrapMethods
  #18 = MethodHandle       #6:#28         // 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;
..........
  #20 = MethodHandle       #6:#29         // invokestatic Test.lambda$main$0:()V
  #21 = NameAndType        #30:#31        // run:()Ljava/lang/Runnable;
.........
    #28 = Methodref          #37.#38        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invokeMethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
{
  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 2: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         5: astore_1
         6: return
      LineNumberTable:
        line 4: 0
        line 5: 6
}
SourceFile: "Test.java"
InnerClasses:
     public static final #43= #42 of #46; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #18 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:
      #19 ()V
      #20 invokestatic Test.lambda$main$0:()V
      #19 ()V
    D:\>java -version
    java version "1.8.0_65"
    Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
    Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)

What happens during runtime?

During runtime, when JVM encounters invokedynamic instruction it makes bootstrap call. This is one time only call and necessary for lambda object construction during runtime. Bootstrap method also creates a CallSite instance that can be used at runtime to execute lambda method.

JVM chooses a strategy to construct lambda. That strategy may involve anonymous inner class generation or MethodHandle (another dynamic language feature added in Java 7, similar to C function pointer that points to an executable code) or dynamic proxies or whatever is good in performance. invokedynamic turns that choice into pure JVM implementation details, hence separating that decision from compile time bytecode.

What is Lambda Runtime Type to a developer?

Let's consider the following example

 public class LambdaTest {

    public void aTestMethod() {
        Runnable runnable = () -> {
            System.out.println("this " + this);
            throw new RuntimeException();
        };
        System.out.println("class:  " + runnable.getClass());
        runnable.run();
    }

    public static void main(String[] args) {
        LambdaTest lambdaTest = new LambdaTest();
        lambdaTest.aTestMethod();
    }
 }

Here we are doing three different things together. Before running lambda run() method we printed the class type of lambda to console, then inside lambda body printed this and purposely threw an exception to see the stack trace.

Let's compile, see some disassembled info (using javap) and run it:

D:\examples\lambda>javac LambdaTest.java

D:\examples\lambda>javap -p LambdaTest
Compiled from "LambdaTest.java"
public class LambdaTest {
  public LambdaTest();
  public void aTestMethod();
  public static void main(java.lang.String[]);
  private void lambda$aTestMethod$0();
}

D:\examples\lambda>java LambdaTest
class:  class LambdaTest$$Lambda$1/791452441
this LambdaTest@1c20c684
Exception in thread "main" java.lang.RuntimeException
        at LambdaTest.lambda$aTestMethod$0(LambdaTest.java:6)
        at LambdaTest.aTestMethod(LambdaTest.java:9)
        at LambdaTest.main(LambdaTest.java:14)
  • Runtime class type of a lambda: com.logicbig.tests.LambdaTest$$Lambda$1/381259350. This is how a lambda expression looks like after JVM has constructed lambda dynamically during runtime. You might also be wondering what the actual type of the lambda expression is. For sure It's not Runnable type in our example, so HotSpot JVM is not doing anonymous class approach during runtime. So what it is? It seems that proxy-like class specific for lambda express is being generated.
  • this printed the enclosing class LambdaTest. That makes sense because unlike anonymous classes they are just functional methods and have no meanings of this for themselves.
  • The last line in the stack trace: at com.logicbig.tests.LambdaTest.lambda$aTestMethod$0( LambdaTest.java:6 ). On looking closely, this call is the desugared lambda method generated during compile time (see last javap output).

Let's use some reflection to find more information about lambda$aTestMethod$0 method we saw above.

import java.lang.reflect.Method;

public class LambdaTest {

    public void aTestMethod() {
        Runnable runnable = () -> {
            System.out.println("this " + this);
            //throw new RuntimeException();
        };
        System.out.println("class:  " + runnable.getClass());
        runnable.run();
    }

    public static void main(String[] args) {
        //  LambdaTest lambdaTest = new LambdaTest();
        // lambdaTest.aTestMethod();

        for (Method method : LambdaTest.class.getDeclaredMethods()) {
            System.out.println(method);
        }

    }
}

Here's the output:

D:\examples\lambda>java LambdaTest
public static void LambdaTest.main(java.lang.String[])
public void LambdaTest.aTestMethod()
private void LambdaTest.lambda$aTestMethod$0()

That confirms that lambda$aTestMethod$0 method is generated during 'compile' time, hence it's a desugared method for JVM linkage

See Also