Explaining invokedynamic. Lambda. Part VII

This is seventh part of mini-series of Explaining invokedynamic. This is the full list of all articles:

Introduction. Part I

Toy example. Part II

Bootstrap method . Part III

Number multiplication (almost) complete example. Part IV

Dynamical hashCode implementation. Part V

Java 9 String concatenation. Part VI

Lambda. Part VII

Records. Part VII

Final notes. Part IX

On the previous part we have seen how you can change string concatenation strategy without changing bytecode.

On this example we will look on historically first usage of invokedynamic as part of the implementation of Java Language features.

Lambda

Let’s look on concrete example.

Let’s look on signature of the filter method. This method is define on Stream and this is it’s signature:

public Stream<T> filter(Predicate<? super T> predicate)

Predicate is a functional interface whose functional method is:

public boolean test(T t)

Note: Roughly speaking, functional interface is interface that has only one simple abstract method (SAM).

What we conceptually want from compiler is to transform our code in such a way as if we have passed Predicate implementation with test method that looks as following:

public static boolean test(String t){
return t.length() > 3;
}

There are may other possible implementations of this conceptual model.

  • For example, we can use dynamic proxy . This options was rejected due to high performance penalty, but this mechanism can be more optimized and utilized.
  • We can “just” use MethodHandle(without CallSite, that is without invokedynamic). At the time of writing JDK 8 performance of MethodHandlewas not so good, so where this can be part of the solution to go, it was unclear whether this is good way to go (Lambda was added as part of JKD 8).
  • We can “just” use inner class instance (where the compiler spins the inner class). This is the most direct implementations of this conceptual model. The main drawback here we’re spinning one new class per lambda expression. Maybe, inner classes implementation can be improved? May be, at least, we can share the same instance for inner class when applicable? Well, may be. But we don’t want to be stucked with inner class behavior forever. As I show above, there are another alternatives.

Basically, we don’t want conflates binary representation with implementation. We want to have freedom to change implementation without changing bytecode. This is similar to String concatenation. We’re emitting the same bytecode in compile-time. Than in runtime we can change the used strategy. Here, we want to store some recipe about lambda expression in the byte-code. Then we want that bootstrap method for lambda expression will use this recipe to use state-of-the art implementation for execution it.

Again, we want to enable us to optimize lambda usage later. Maybe, we will switch entirely from one strategy to another. In order to enable us to achieve this invokedynamic way is perfect fit.

Once the JVM sees invokedynamic in this example for the first time, it calls the bootstrap method. As of writing this article, the lambda bootstrap method will use the [LambdaMetafactory] generate an inner class for the lambda at runtime.

Then the bootstrap method encapsulates the generated inner class inside a special type of CallSite known as ConstantCallSite. This type of CallSite would never change after setup. Therefore, after the first setup for each lambda, the JVM will always use the fast path to directly call the lambda logic.

Although this is the most efficient type of invokedynamic, it’s certainly not the only available option. As a matter of fact, Java provides MutableCallSite and VolatileCallSite to accommodate for more dynamic requirements.

https://www.baeldung.com/java-invoke-dynamic

  • Invoked with invokedynamic, returns a lambda object.
  • The bootstrap method is called the lambda metafactory.

Similar to all other bootstrap methods, this one takes at least three arguments as follows:

The Ljava/lang/invoke/MethodHandles$Lookup argument represents the lookup context for the invokedynamic

The Ljava/lang/String represents the method name in the call site — in this example, the method name is test

The Ljava/lang/invoke/MethodType is the dynamic method signature of the call site — in this case, it’s ()Ljava/util/function/Predicate

In addition to these three arguments, bootstrap methods also can optionally accept one or more extra parameters. In this example, these are the extra ones:

The (Ljava/lang/Object;)Z is an erased method signature accepting an instance of Object and returning a boolean.

The REF_invokeStatic Main.lambda$main$0:(Ljava/lang/String;)Z is the MethodHandlepointing to the actual lambda logic.

The (Ljava/lang/String;)Z is a non-erased method signature accepting one String and returning a boolean.

Put simply, the JVM will pass all the required information to the bootstrap method. Bootstrap method will, in turn, use that information to create an appropriate instance of Predicate. Then, the JVM will pass that instance to the filter method.

https://www.baeldung.com/java-invoke-dynamic

  • Static arguments describe the behavior and target type.
  • Dynamic arguments are captured variables (if any). There is none in our case.

Note:

There is also alternate version of the the bootstrap method (lambda metafactory). It adds the ability to manage the following attributes of function objects:

* Bridging. It is sometimes useful to implement multiple variations of the method signature, involving argument or return type adaptation. This occurs when multiple distinct VM signatures for a method are logically considered to be the same method by the language…

* Multiple interfaces. If needed, more than one interface can be implemented by the function object. (These additional interfaces are typically marker interfaces with no methods…)

* Serializability. The generated function objects do not generally support serialization. If desired, FLAG_SERIALIZABLE can be used to indicate that the function objects should be serializable. Serializable function objects will use, as their serialized form, instances of the class SerializedLambda, which requires additional assistance from the capturing class (the class described by the MethodHandles.Lookup parameter caller); see SerializedLambda for details.

I am not going to cover any of this here, but all of these are important parts of the Lambda feature.

Let’s go back to our recipe to be built. If our lamdba expression doesn’t capture any effectively final variables, if doesn’t use any instance method, that is, it is very simple, than it is enough to capture all static parameters type, the functional interface method and actual parameters value. This information should be emitted by compiler for invokedynamic and will be used by bootstrap method (lambda metafactory).

Note:

  • It lambda expression captures any effectively final variables it will passed as addition parameter to bootstrap method (lambda metafactory).
  • If lambda expression uses any instance method than the implementation function should be method. That is, it should be function and not static function as in simple case.

Enough on what I’m not going to cover. :-) Let’s see actual code that is emitted in our case.

The Java compiler generated the following funny-looking static method:

So, for our simple lambda expression, what compilers emits bytecode that encoded all static parameters type, the functional interface method and actual parameters value. It emits inkovedynamic bytecode instruction with bootstrap method as this lambda metafactory. This lambda metafactory is ConstantCallSite with MethodHandle that is binds to the lambda$main$0 function above.

Note: Because lambda$main$0 is stateless, we can bound multiple CallSites to same MethodHandle implementaion.

Senior Software Engineer at Pursway