Why Are Java 8 Lambdas Invoked Using Invokedynamic

Why Java-8 lambda need invokeDynamic byteCode to invoke an interface method

The method holding the body of the lambda expression is not invoked via invokedynamic. The implementation of the functional interface is invoked via invokeinterface, just like any other interface method. The caller even doesn’t know whether an object implementing an interface has been generated for a lambda expression.

How this generated implementation of the interface invokes the synthetic method, is JRE specific, but usually it happens via an ordinary invocation (invokestatic, invokevirtual or invokespecial). Only the access rights follow different rules, i.e. the method can be invoked by the generated class despite being private.

The invokedynamic instruction is used for the instantiation of the interface implementation, so it allows arbitrary, unknown implementation classes, including classes, which do not exist at compile-time, but are generated at runtime. It also opens the possibility to return an existing instance instead of creating a new one, which is impossible for ordinary instance creation code.

java 8 invoke dynamic advantage

For the first question:

Simply spoken: invokedynamic was very carefully designed (thus it took so long to get it into Java); and one of the side effects of that: is extremely efficient performance wise.

See here for some further reading. Quoting from there:

invokedynamic also benefits dynamic language implementers by supporting dynamically changing call site targets -- a call site, more specifically, a dynamic call site is an invokedynamic instruction. Furthermore, because the JVM internally supports invokedynamic, this instruction can be better optimized by the JIT compiler.

So, long story short: at least theoretically, when you can write code that uses invokedynamic instead of some "old school" way of solving the same problem, new invokedynamic solution has a certain chance of being "faster". As in: it is faster to use invokedynamic compared to the old-school way of creating an anonymous inner class and use that.

Invokedynamic factory creates lambdas as a singleton?

You need to understand what a call-site is, first, imo; to be able to understand where caching happens. Both exec1 and exec2 will create two separate instances of a Runnable interface; both will be cached on the call-site. May be this little snippet will help:

public static void main(String[] args) {
useStatelessLambda1();
useStatelessLambda1();

useStatelessLambda2();
useStatelessLambda2();
}

static void useStatelessLambda1() {
Runnable exec1 = () -> {
System.out.print("Hi from lambda");
};

System.out.print(exec1.hashCode() + " ");
exec1.run();
System.out.println("\n");
}

static void useStatelessLambda2() {
Runnable exec2 = () -> {
System.out.print("Hi from lambda");
};

System.out.print(exec2.hashCode() + " ");
exec2.run();
System.out.println("\n");
}

Running this reveals:

1878246837  Hi from lambda

1878246837 Hi from lambda

1995265320 Hi from lambda

1995265320 Hi from lambda

separate instances, but both cached on the call-site.

Either way, looking at the byte-code will not tell you anything about that. what you could look at is the bootstrap method that invokedynamic will use : LambdaMetafactory::metafactory and understand what that will do.

How does invokedynamic instruction decide its description?

The invokedynamic instruction does not mandate any signature. It’s the code generator which decides for a particular signature and bootstrap method to use, which together define the actual semantic.

E.g., the signature and its meaning is entirely different when using StringConcatFactory.makeConcat(…) instead of LambdaMetafactory.metafactory(…) as bootstrap method.

The documentation of these methods and their containing classes describes the behavior thoroughly. But before digging into the bytecode details you should first understand the source code features, i.e. of capturing method references, as explained in What is the equivalent lambda expression for System.out::println. The method reference System.out::println captures the PrintStream found in the static field System.out when instantiating the Consumer. Hence, the generated factory method consumes a PrintStream and produces a Consumer, resulting in the signature (Ljava/io/PrintStream;)Ljava/util/function/Consumer;.

So, before executing the invokedynamic instruction, the field has to be read with a GETSTATIC java/lang/System.out instruction. The DUP; INVOKEVIRTUAL getClass() sequence is part of an intrinsic null check which has been discussed in In Java Lambda's why is getClass() called on a captured variable

What's invokedynamic and how do I use it?

It is a new JVM instruction which allows a compiler to generate code which calls methods with a looser specification than was previously possible -- if you know what "duck typing" is, invokedynamic basically allows for duck typing. There's not too much you as a Java programmer can do with it; if you're a tool creator, though, you can use it to build more flexible, more efficient JVM-based languages. Here is a really sweet blog post that gives a lot of detail.

Invokedynamic with non-static context

It’s not correct to say that lambda expressions were always compiled to a static method. It’s not specified, how they are compiled, which leaves room for two different strategies for a lambda expression that captures this, like your s -> System.out.println(this).

  1. use an instance method:

    private void compiler$chosen$name(String s) {
    System.out.println(this);
    }
  2. use a static method:

    private static void compiler$chosen$name(TypeOfThis var0, String s) {
    System.out.println(var0);
    }

Both methods work equally well when the invokedynamic instruction points to a bootstrap method in the LambdaMetafactory. In either case, the invokedynamic instruction will have a signature consuming a TypeOfThis instance and producing a Consumer<String>. From the documentation of the LambdaMetafactory, you can derive that it will treat the receiver of a non-static target method like an implied the first argument, which makes the functional signature of both variants identical. All that matters, is that the argument to the consumer’s accept method has to correspond to the last argument of the list.

I’ve encountered both strategies in practice, so this is indeed compiler dependent.

Note that these strategies also work on source code level, when using method references:

public class Example {
BiConsumer<Example,String> variant1 = Example::instanceMethod;
BiConsumer<Example,String> variant2 = Example::staticMethod;

private void instanceMethod(String s) {
System.out.println(this);
}

private static void staticMethod(Example instance, String s) {
System.out.println(instance);
}
}

This demonstrates the equivalence of a method receiver and the the first argument to a static method. However, when it comes to binding an argument, only Consumer<String> c = this::instanceMethod; works with method references. The other binding features of the LambdaMetafactory are only used by compiler generated code for lambda expressions.

How is stored the result of the invokedynamic?

Each invokedynamic bytecode refers to a corresponding CONSTANT_InvokeDynamic_info structure in the constant pool. This structure contains a Method Descriptor that is used to derive the types of the arguments and the type of return value for this invokedynamic instruction.

In your example the method descriptor is ()LI; computed during source-to-bytecode translation.

8: invokedynamic #4,  0              // InvokeDynamic #0:foo:()LI;
^^^^^

It means that this particular bytecode expects no arguments and always produces the result of type I.

Lambda Expressions in Java8

In the following line

String s = (String) invoke(() -> true);

It is actually invoke(Callable) that is getting called. The reason is:

  • () -> true is a lambda expression that has zero formal parameter and return a result.
  • Such a signature (zero parameter, single result) is compatible with the functional method call() of the Callable interface. Note that the interface does not need to have the @FunctionalInterface annotation, it just needs to have a single abstract method.

If you want to invoke invoke(Runnable) instead, you will need to create a lambda that is compatible with a functional method that takes zero parameter and returns no result (i.e. conforms with the signature of run()). Something like this:

invoke(() -> System.out.println("foo"));

Which just prints foo when ran.



Related Topics



Leave a reply



Submit