Wednesday, October 5, 2016

Java 8 Lambda Expressions - How does it work?

Introduction

You might wonder how the Lambda expression works in Java 8. What happens during the compile time? How does the compiler handle this? And also, how does the JVM able to run the method at run time. The only answer to these questions is just to follow this post. 

In this post we will discuss about the internal working of Lambda expression.

What does the Compiler do?

Sugaring and De-sugaring

I would like to recall what is sugaring here. Sugaring means make something sweet by keeping it simple, more clear and concise. When we apply sugaring to our code we get simple expression like Lambda expression. Hence, Lambda expression is a sugared code.

When you have Lambda expression in your code the compiler feels the sweet in that and it knows the JVM don't like this. Hence, it de-sugars that code. In other words it makes that anonymous method verbose as below. For our discussion take the example of creating a thread using Runnable functional interface.
compiler de-sugar the code
De-sugaring
As usual the compiler prepares the byte code instructions, but this time it adds special instruction for evaluating the Lambda expression. It adds invokeDynamic byte code instruction which has details of the target functional interface like its type(Runnable in our case), its method name(run() in our case), its parameters(no parameters in our case) and return type (void in our case).
Bytecode instructions

How does the JVM handles the byte code instructions?

Linkage

Once the invokedynamic instruction is encountered the JVM makes the call to the factory called LambdaMetaFactory. This factory has few bootstrap methods for serving the JVM request. The factory uses the given byte code instructions to create an implementation class(the name takes the format of 'X$$Lambda$1') which would implement the target functional interface(Runnable).

If there is no parameters in the target functional interface method then the LambdaMetaFactory creates an instance of the implementation class (called as Function Object) and attaches a MethodHandle to it. Now, the meta factory creates and returns the LambdaFactory (called as CallSite) with the attached method handle.

If there are some target method parameters then the lambda meta factory generates and attaches  the MethodHandle to the implementation class's constructor. In this case the LambdaFactory(CallSite) takes care of creating the Function object and attaching MethodHandle to it. 

In short, when the JVM invokes the invoke dynamic instruction it calls the LambdaMetaFactory which creates and returns the LambdaFactory(CallSite) with the MethodHandle to the target type implementation class. This entire process is called Linkage. Now, the method handle which is the target field of the CallSite will be retuned to the JVM.

This linkage process happens only for the first time for the given target type functional interface. The further execution on the same target type functional interface will happen through the     LambdaFactory (CallSite) directly. The call on the CallSite's target will return the existing function object.

Capture

The JVM now invokes the CallSite's target(MethodHandle) and the corresponding function object is returned. This process is called Capture.

Invocation

Once the function object is returned a call to the implementation method(de-sugared code) happens and the code is executed. This is process is called Invocation.

The above process is explained in the below figure.
Evaluation of Lambda Expression

Conclusion

If you have already gone through 'Java 8 Lambda Expressions - The What and Why' post you could recollect the example we discussed on the AWT event handling where the instance of type ActionListener will be registered to the event source for notification. Similarly, here the function object is registered with the Thread for notification. Hope you enjoyed reading the post!


No comments:

Post a Comment

Do you know - Series - 4: Boxing and Unboxing - Integer assignment and comparison

We know about boxing and unboxing in Java. It is about automatic conversion of primitive type value into its Wrapper Object equivalent type...