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?


Solution 1:

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.

Solution 2:

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.

Solution 3:

Current Java 8's lambda implementation is a compound decision:

    1. Compile the lambda expression to a static method in the enclosing class; instead of compiling lambdas to separate inner class files (Scala compiles this way, which generates many $$$ class files)
    1. Introduce a constant pool: BootstrapMethods, which wraps the static method invocation to callsite object (can be cached for later use)

So to answer your question,

    1. the current lambda implementation using invokedynamic is a little bit faster than the separate inner class way, because no need to load these inner class files, but instead create the inner class byte[] on the fly (to satisfy for example the Function interface), and cached for later use.
    1. JVM team may still choose to generate separate inner class (by referencing the enclosing class's static methods) files: it's flexible