Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are Java 8 lambdas invoked using invokedynamic?

The invokedynamic instruction is used to help the VM determine the method reference at runtime instead hardwiring it at compile time.

This is useful with dynamic languages where the exact method and argument types aren't known until runtime. But that isn't the case with Java lambdas. They are translated to a static method with well defined arguments. And this method can be invoked using invokestatic.

So then what is the need of invokedynamic for lambdas, especially when there is a performance hit?

like image 248
Kshitiz Sharma Avatar asked May 02 '15 12:05

Kshitiz Sharma


People also ask

Why are lambdas expression used in Java 8?

Lambda expression is a new and important feature of Java which was included in Java SE 8. It provides a clear and concise way to represent one method interface using an expression. It is very useful in collection library. It helps to iterate, filter and extract data from collection.

What is Invokedynamic in Java?

A: invokedynamic is a bytecode instruction that facilitates the implementation of dynamic languages (for the JVM) through dynamic method invocation. This instruction is described in the Java SE 7 Edition of the JVM Specification.

Why do I need a functional interface to work with lambdas?

Surely lambda expression can be one-time used as your commented code does, but when it comes to passing lambda expression as parameter to mimic function callback, functional interface is a must because in that case the variable data type is the functional interface.

What are the characteristics of a Java 8 lambda expression?

Characteristics of Lambda ExpressionOptional Type Declaration − There is no need to declare the type of a parameter. The compiler inferences the same from the value of the parameter. Optional Parenthesis around Parameter − There is no need to declare a single parameter in parenthesis.


2 Answers

Lambdas are not invoked using invokedynamic, their object representation is created using invokedynamic, the actual invocation is a regular invokevirtual or invokeinterface.

For example:

// creates an instance of (a subclass of) Consumer  // with invokedynamic to java.lang.invoke.LambdaMetafactory  something(x -> System.out.println(x));     void something(Consumer<String> consumer) {       // invokeinterface       consumer.accept("hello");  } 

Any lambda has to become an instance of some base class or interface. That instance will sometimes contain a copy of the variables captured from the original method and sometimes a pointer to the parent object. This can be implemented as an anonymous class.

Why invokedynamic

The short answer is: to generate code in runtime.

The Java maintainers chose to generate the implementation class in runtime. This is done by calling java.lang.invoke.LambdaMetafactory.metafactory. Since the arguments for that call (return type, interface, and captured parameters) can change, this requires invokedynamic.

Using invokedynamic to construct the anonymous class in runtime, allows the JVM to generate that class bytecode in runtime. The subsequent calls to the same statement use a cached version. The other reason to use invokedynamic is to be able to change the implementation strategy in the future without having to change already compiled code.

The road not taken

The other option would be the compiler creating an innerclass for each lambda instantiation, equivalent to translating the above code into:

something(new Consumer() {      public void accept(x) {        // call to a generated method in the base class        ImplementingClass.this.lambda$1(x);         // or repeating the code (awful as it would require generating accesors):        System.out.println(x);     } );    

This requires creating classes in compile time and having to load then during runtime. The way jvm works those classes would reside in the same directory as the original class. And the first time you execute the statement that uses that lambda, that anonymous class would have to be loaded and initialized.

About performance

The first call to invokedynamic will trigger the anonymous class generation. Then the opcode invokedynamic is replaced with code that's equivalent in performance to the writing manually the anonymous instantiation.

like image 154
Daniel Sperry Avatar answered Oct 01 '22 01:10

Daniel Sperry


Brain Goetz explained the reasons for the lambda translation strategy in one of his papers which unfortunately now seem unavailable. Fortunately I kept a copy:

Translation strategy

There are a number of ways we might represent a lambda expression in bytecode, such as inner classes, method handles, dynamic proxies, and others. Each of these approaches has pros and cons. In selecting a strategy, there are two competing goals: maximizing flexibility for future optimization by not committing to a specific strategy, vs providing stability in the classfile representation. We can achieve both of these goals by using the invokedynamic feature from JSR 292 to separate the binary representation of lambda creation in the bytecode from the mechanics of evaluating the lambda expression at runtime. Instead of generating bytecode to create the object that implements the lambda expression (such as calling a constructor for an inner class), we describe a recipe for constructing the lambda, and delegate the actual construction to the language runtime. That recipe is encoded in the static and dynamic argument lists of an invokedynamic instruction.

The use of invokedynamic lets us defer the selection of a translation strategy until run time. The runtime implementation is free to select a strategy dynamically to evaluate the lambda expression. The runtime implementation choice is hidden behind a standardized (i.e., part of the platform specification) API for lambda construction, so that the static compiler can emit calls to this API, and JRE implementations can choose their preferred implementation strategy. The invokedynamic mechanics allow this to be done without the performance costs that this late binding approach might otherwise impose.

When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression, possibly with some additional arguments (for values captured from the lexical scope, if any.) At the point at which the lambda expression would be captured, it generates an invokedynamic call site, which, when invoked, returns an instance of the functional interface to which the lambda is being converted. This call site is called the lambda factory for a given lambda. The dynamic arguments to the lambda factory are the values captured from the lexical scope. The bootstrap method of the lambda factory is a standardized method in the Java language runtime library, called the lambda metafactory. The static bootstrap arguments capture information known about the lambda at compile time (the functional interface to which it will be converted, a method handle for the desugared lambda body, information about whether the SAM type is serializable, etc.)

Method references are treated the same way as lambda expressions, except that most method references do not need to be desugared into a new method; we can simply load a constant method handle for the referenced method and pass that to the metafactory.

So, the idea here seemed to be to encapsulate the translation strategy and not commit to a particular way of doing things by hiding those details. In the future when type erasure and lack of value types have been solved and maybe Java supports actual function types, they might just as well go there and change that strategy for another one without causing any problems in the users' code.

like image 44
Edwin Dalorzo Avatar answered Oct 01 '22 01:10

Edwin Dalorzo