Invokedynamic 101

1 2 3 Page 2
Page 2 of 3

Listing 4. MHD.java (version 4)

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MHD
{
   public static void main(String[] args) throws Throwable
   {
      MethodHandles.Lookup lookup = MethodHandles.lookup();
      MethodHandle mh = lookup.findStatic(Math.class, "pow",
                                          MethodType.methodType(double.class,
                                                                double.class,
                                                                double.class));
      mh = MethodHandles.insertArguments(mh, 1, 10);
      System.out.printf("2^10 = %d%n", (int) (double) mh.invoke(2.0));
   }
}

Listing 4 uses a combinator (a method that combines or transforms a pre-existing method handle into a new method handle) to modify the method handle to the pow() method. In this case, the combinator is MethodHandles's MethodHandle insertArguments(MethodHandle target, int pos, Object... values) method, which inserts a bound argument (10) into the pre-existing method handle. When invoke() (or invokeExact()) is subsequently called on the new method handle, only a single argument is required.

Notice the double cast -- (int) (double). invoke()'s return value must be cast to double type to be compatible with the previously specified method type when the method handle was obtained (via the findStatic() method call). Because System.out.printf() requires an int value (because of %d), the double value must be cast to int.

Compile Listing 4 (javac MHD.java) and run the application (java MHD). You should observe the following output:

2^10 = 1024

Bootstrapping

Q: I understand that executing an invokedynamic instruction results in the execution of a method handle, which may be a chain of combinators. How does invokedynamic know which method handle to execute?

A: At runtime, an invokedynamic call site is bound to a method handle by way of a bootstrap method. This method is executed the first time the JVM encounters this call site during execution.

Q: How does invokedynamic locate the bootstrap method?

A: The invokedynamic instruction is followed by an operand that serves as an index into the classfile's constant pool. The value in the constant pool at this index is a CONSTANT_InvokeDynamic structure. This structure includes an index into the classfile's BootstrapMethods attribute, which ultimately identifies the bootstrap method.

Q: What does the bootstrap method header look like?

A: A typical bootstrap header appears below:

public static CallSite bootstrap(Lookup lookup, String name, MethodType type)
   throws Throwable

The JVM invokes this method with the following arguments:

  • lookup: this lookup object is bound to those classes that are visible in the context of the call site (the invokedynamic instruction)
  • name: the (typically fictitious) name of a method to invoke -- the bootstrap method determines the identity of the method handle being invoked
  • type: the type of the method being invoked, and which is expected by the call site

The bootstrap method can be called with up to an additional 251 static arguments taken from the constant pool. Also, it's declared to throw a Throwable instance.

The bootstrap method returns a java.lang.invoke.CallSite subclass instance. The type of the call site's target must be exactly equal to the type derived from the call site's type, which is passed to the bootstrap method as the type argument. The CallSite instance is said to contain the method handle and becomes permanently linked to the call site (the invokedynamic instruction). Although the CallSite instance refers to the same program location (invokedynamic instruction) throughout its lifetime, it may allow its target method handle to be redefined, which lets you replace code during execution.

Q: Can you provide an example of a bootstrap method?

A: Check out Listing 5.

Listing 5. IDDL.java

import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class IDDL
{
   private static MethodHandle hw;

   private static void hw() 
   {
      System.out.println("Hello, World!");
   }

   public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, 
                                           String name, 
                                           MethodType type)
      throws IllegalAccessException, NoSuchMethodException
   {
      MethodHandles.Lookup lookup = MethodHandles.lookup();
      Class thisClass = lookup.lookupClass();
      hw = lookup.findStatic(thisClass, "hw", 
                             MethodType.methodType(void.class));
      if (!type.equals(hw.type()))
         hw = hw.asType(type);

      return new ConstantCallSite(hw);
   }
}

Listing 5 identifies an IDDL (InvokeDynamic Demo Linkage) class that reveals a target hw() method, and a bootstrapDynamic() method that creates a method handle to hw() and returns a java.lang.ConstantCallSite that wraps this method handle.

bootstrapDynamic() reveals that the bootstrap's method name is changeable. It also shows that instead of specifying throws Throwable, you can list the exact exceptions being thrown via the throws clause.

The caller lookup object isn't used by this bootstrap method because that object refers to the class in which the invokedynamic call occurs -- you'll see this class later. Instead, we need a lookup object that can find hw() in the IDDL class, which is the reason for MethodHandles.lookup().

MethodHandles.Lookup declares a Class<?> lookupClass() method that identifies the class performing the lookup. I pass this method's return value to findStatic() as its first argument. Note that I could have passed IDDL.class instead (because IDDL.class is the Class object returned from lookupClass()), but chose to pass lookupClass()'s return value to show more of the API.

findStatic() is also passed hw as the target method name and MethodType.methodType(void.class) to signify that this method has a void return type and no parameters.

It's important that the method handle's type match the call site's expected type, and this is especially true when combinators are used. Essentially, you compare the type parameter value with the method handle's type, which is obtained by invoking MethodHandle's MethodType type() method. If they differ, you invoke MethodHandle's MethodHandle asType(MethodType newType) method with the expected type (the type parameter value). This method returns an adapter method handle that adapts the type of the current method handle to the type passed to asType().

Finally, you must return an appropriate CallSite instance that wraps the target method handle. Because CallSite is abstract, you would use one of its subclasses, such as java.lang.invoke.ConstantCallSite, which is a CallSite whose target never changes.

Q: I would like to play with IDDL. Can you provide a class with an invokedynamic instruction that calls bootstrapDynamic()?

A: I've created an IDD (InvokeDynamic Demo) class whose main() method executes the invokedynamic instruction. Furthermore, this instruction links to IDDL's bootstrapDynamic() method.

Because the Java 7 compiler doesn't emit the invokedynamic instruction, IDD must be created programmatically. One way to accomplish this task is to use a bytecode manipulation framework, such as ASM, to create an application that generates IDD.class. Alternatively, you could use Listing 6.

Listing 6. IDDG.java

import java.io.FileOutputStream;
import java.io.IOException;

public class IDDG // InvokeDynamic Demo Generator
{
   static short[] _bytes =
   {
      0xCA, 0xFE, 0xBA, 0xBE, 0x00, 0x00, 0x00, 0x33,
      0x00, 0x17, 0x01, 0x00, 0x03, 0x49, 0x44, 0x44,
      0x07, 0x00, 0x01, 0x01, 0x00, 0x10, 0x6A, 0x61,
      0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F,
      0x4F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x07, 0x00,
      0x03, 0x01, 0x00, 0x06, 0x3C, 0x69, 0x6E, 0x69,
      0x74, 0x3E, 0x01, 0x00, 0x03, 0x28, 0x29, 0x56,
      0x0C, 0x00, 0x05, 0x00, 0x06, 0x0A, 0x00, 0x04,
      0x00, 0x07, 0x01, 0x00, 0x04, 0x6D, 0x61, 0x69,
      0x6E, 0x01, 0x00, 0x16, 0x28, 0x5B, 0x4C, 0x6A,
      0x61, 0x76, 0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67,
      0x2F, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3B,
      0x29, 0x56, 0x01, 0x00, 0x04, 0x49, 0x44, 0x44,
      0x4C, 0x07, 0x00, 0x0B, 0x01, 0x00, 0x10, 0x62,
      0x6F, 0x6F, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70,
      0x44, 0x79, 0x6E, 0x61, 0x6D, 0x69, 0x63, 0x01,
      0x00, 0x73, 0x28, 0x4C, 0x6A, 0x61, 0x76, 0x61,
      0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E,
      0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65, 0x74,
      0x68, 0x6F, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C,
      0x65, 0x73, 0x24, 0x4C, 0x6F, 0x6F, 0x6B, 0x75,
      0x70, 0x3B, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F,
      0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x53, 0x74, 0x72,
      0x69, 0x6E, 0x67, 0x3B, 0x4C, 0x6A, 0x61, 0x76,
      0x61, 0x2F, 0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69,
      0x6E, 0x76, 0x6F, 0x6B, 0x65, 0x2F, 0x4D, 0x65,
      0x74, 0x68, 0x6F, 0x64, 0x54, 0x79, 0x70, 0x65,
      0x3B, 0x29, 0x4C, 0x6A, 0x61, 0x76, 0x61, 0x2F,
      0x6C, 0x61, 0x6E, 0x67, 0x2F, 0x69, 0x6E, 0x76,
      0x6F, 0x6B, 0x65, 0x2F, 0x43, 0x61, 0x6C, 0x6C,
      0x53, 0x69, 0x74, 0x65, 0x3B, 0x0C, 0x00, 0x0D,
      0x00, 0x0E, 0x0A, 0x00, 0x0C, 0x00, 0x0F, 0x0F,
      0x06, 0x00, 0x10, 0x01, 0x00, 0x03, 0x66, 0x6F,
      0x6F, 0x0C, 0x00, 0x12, 0x00, 0x06, 0x12, 0x00,
      0x00, 0x00, 0x13, 0x01, 0x00, 0x04, 0x43, 0x6F,
      0x64, 0x65, 0x01, 0x00, 0x10, 0x42, 0x6F, 0x6F,
      0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x4D, 0x65,
      0x74, 0x68, 0x6F, 0x64, 0x73, 0x00, 0x21, 0x00,
      0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
      0x02, 0x00, 0x01, 0x00, 0x05, 0x00, 0x06, 0x00,
      0x01, 0x00, 0x15, 0x00, 0x00, 0x00, 0x11, 0x00,
      0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x2A,
      0xB7, 0x00, 0x08, 0xB1, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x09, 0x00, 0x09, 0x00, 0x0A, 0x00, 0x01,
      0x00, 0x15, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00,
      0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0xBA, 0x00,
      0x14, 0x00, 0x00, 0xB1, 0x00, 0x00, 0x00, 0x00,
      0x00, 0x01, 0x00, 0x16, 0x00, 0x00, 0x00, 0x06,
      0x00, 0x01, 0x00, 0x11, 0x00, 0x00
   };

   public static void main(String[] args)
   {
      try (FileOutputStream fos = new FileOutputStream("IDD.class"))
      {
         for (short _byte: _bytes)
            fos.write(_byte);
      }
      catch (IOException ioe)
      {
         ioe.printStackTrace();
      }
   }
}

Listing 6 presents the source code to a simple application that generates IDD.class. Compile the source code (javac IDDG.java) and run IDDG.class (java IDDG). You should observe an IDD.class file in the current directory.

You can explore the contents of IDD.class by using the javap tool with its -c (disassemble the code) and -v (print additional information -- verbose) options. For example, executing javap -c -v IDD.class results in this output:

1 2 3 Page 2
Page 2 of 3