javajvmbytecodeinvokedynamic

Understanding Invokedynamic Instruction in Java Bytecode and Its Impact on the Operand Stack


I'm seeking clarity on how the invokedynamic instruction operates in Java bytecode. To illustrate, I've prepared a simple Java code snippet utilizing lambda expressions, which typically leads to the generation of the invokedynamic instruction upon compilation:

public class App{
    public static void main(String... args){
        Runnable r = () -> {};
        r.run();
    }
}

Upon compilation with javac, the following bytecode is produced:

0: invokedynamic #7,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
5: astore_1
6: aload_1
7: invokeinterface #11,  1           // InterfaceMethod java/lang/Runnable.run:()V
12: return

As I understand, after the invokedynamic instruction, the operand stack should contain a CallSite (the result of the bootstrap method), which is subsequently stored onto the operand stack. Then, this CallSite is stored into a variable and executed.

However, the description of the invokedynamic method on Wikipedia states:

[invokedynamic instruction] invokes a dynamic method and puts the result on the stack (might be void);

This statement, particularly "might be void" has left me confused. Does this imply that a bootstrap method may not necessarily return a CallSite at all? Despite my attempts, I haven't been able to find any Java code snippet that generates the invokedynamic instruction without placing any values on the operand stack.

Could someone clarify whether it's possible for the invokedynamic instruction to not place anything onto the operand stack, including the CallSite? I've reviewed related answers on StackOverflow like this, this, and this, but none explicitly address the state of the operand stack. Additionally, I've attempted to find a bytecode debugger to check the operand stack state, but to no avail.

Any insights or guidance on this matter would be greatly appreciated.


Solution

  • You misunderstand what invokedynamic does.

    invokedynamic does not push a CallSite onto the stack. The operands of the invokedynamic instruction refers to a method that returns a CallSite. In the case of lambda expressions, this is the LambdaMetafactory.metafactory method.

    invokedynamic calls this CallSite-returning method (metafactory), gets a CallSite, then calls the target method handle indicated by CallSite. If this method handle needs arguments, things on the operand stack are popped. Finally, the result of calling this method handle is pushed onto the stack.

    In this case, the call site returned by metafactory refers to some method that does not take arguments, and returns an implementation of Runnable. invokedynamic pushes this Runnable onto the stack. It is then stored into a local variable, loaded from that same variable (astore, aload), and its run method is called with invokeinterface.

    metafactory takes a few arguments, and you might be wondering how invokedynamic knows what arguments to pass. Part of it is just "built into" invokedynamic, and the rest is stored in the BootstrapMethods attribute of the class file.

    Roughly translating this into Java code, the whole process looks like this:

    Runnable r = LambdaMetafactory.metafactory(XXX)
        .getTarget().invokeExact();
    

    where the arguments "XXX" are in the BootstrapMethods attribute.

    As another example, string concatenation uses invokedynamic too. This time, the operands will refer to one of the methods in StringConcatFactory. The method will return a CallSite representing some method that will return the concatenated string. For an expression like "Foo" + System.nanoTime() + "Bar", invokedynamic would do something like:

    StringConcatFactory.makeConcatWithConstants(XXX)
        .getTarget().invokeExact(System.nanoTime())
    

    For more info, see the JVMS.