Invokedynamic 101

Oracle's Java 7 release introduced a new invokedynamic bytecode instruction to the Java Virtual Machine (JVM) and a new java.lang.invoke API package to the standard class library. This post introduces you to this instruction and API.

The what and how of invokedynamic

Q: What is invokedynamic?

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.

Q: How does invokedynamic facilitate dynamic language implementation?

A: In a dynamic language, type-checking typically occurs at runtime. Developers must pass appropriate types or risk runtime failures. It's often the case that java.lang.Object is the most accurate type for a method argument. This situation complicates type checking, which impacts performance.

Another challenge is that dynamic languages typically offer the capability to add fields/methods to and remove them from existing classes. As a result, it's necessary to defer class, method, and field resolution to runtime. Also, it's often necessary to adapt a method invocation to a target that has a different signature.

These challenges have traditionally required ad hoc runtime support to be built on top of the JVM. This support includes wrapper type classes, using hash tables to provide dynamic symbol resolution, and so on. Bytecode is generated with entry points to the runtime in the form of method calls using any of the four method-invocation instructions:

  • invokestatic is used to invoke static methods.
  • invokevirtual is used to invoke public and protected non-static methods via dynamic dispatch.
  • invokeinterface is similar to invokevirtual except for the method dispatch being based on an interface type.
  • invokespecial is used to invoke instance initialization methods (constructors) as well as private methods and methods of a superclass of the current class.

This runtime support affects performance. Generated bytecode often requires several actual JVM method invocations for one dynamic language method invocation. Reflection is widely used and contributes to performance degradation. Also, the many different execution paths make it impossible for the JVM's just-in-time (JIT) compiler to apply optimizations.

To address poor performance, the invokedynamic instruction does away with ad hoc runtime support. Instead, the first call bootstraps by invoking runtime logic that efficiently selects a target method, and subsequent calls typically invoke the target method without having to re-bootstrap.

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.

Method handles

Q: I understand that invokedynamic works with method handles to facilitate dynamic method invocation. What is a method handle?

A: A method handle is "a typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values." In other words, it's similar to a C-style function pointer that points to executable code -- a target -- and which can be dereferenced to invoke this code. Method handles are described by the abstract java.lang.invoke.MethodHandle class.

Q: Can you provide a simple example of method handle creation and invocation?

A: Check out Listing 1.

Listing 1. MHD.java (version 1)

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(MHD.class, "hello",
                                          MethodType.methodType(void.class));
      mh.invokeExact();
   }

   static void hello()
   {
      System.out.println("hello");
   }
}

Listing 1 describes a method handle demonstration program consisting of main() and hello() class methods. This program's goal is to invoke hello() via a method handle.

main()'s first task is to obtain a java.lang.invoke.MethodHandles.Lookup object. This object is a factory for creating method handles and is used to search for targets such as virtual methods, static methods, special methods, constructors, and field accessors. Furthermore, it's dependent on a call site's invocation context and enforces method handle access restrictions each time a method handle is created. In other words, a call site (such as Listing 1's main() method acting as a call site) that obtains a lookup object can only access those targets that are accessible to the call site. The lookup object is obtained by invoking the java.lang.invoke.MethodHandles class's MethodHandles.Lookup lookup() method.

After obtaining the lookup object, this object's MethodHandle findStatic(Class<?> refc, String name, MethodType type) method is called to obtain a method handle to the hello() method. The first argument passed to findStatic() is a reference to the class (MHD) from which the method (hello()) is accessed, and the second argument is the method's name. The third argument is an example of a method type, which "represents the arguments and return type accepted and returned by a method handle, or the arguments and return type passed and expected by a method handle caller." It's represented by an instance of the java.lang.invoke.MethodType class, and obtained (in this example) by calling java.lang.invoke.MethodType's MethodType methodType(Class<?> rtype) method. This method is called because hello() only provides a return type, which happens to be void. This return type is made available to methodType() by passing void.class to this method.

The returned method handle is assigned to mh. This object is then used to call MethodHandle's Object invokeExact(Object... args) method, to invoke the method handle. In other words, invokeExact() results in hello() being called, and hello being written to the standard output stream. Because invokeExact() is declared to throw Throwable, I've appended throws Throwable to the main() method header.

Q: In your previous answer, you mentioned that the lookup object can only access those targets that are accessible to the call site. Can you provide an example that demonstrates trying to obtain a method handle to an inaccessible target?

A: Check out Listing 2.

Listing 2. MHD.java (version 2)

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

class HW
{
   public void hello1()
   {
      System.out.println("hello from hello1");
   }

   private void hello2()
   {
      System.out.println("hello from hello2");
   }
}

public class MHD
{
   public static void main(String[] args) throws Throwable
   {
      HW hw = new HW();
      MethodHandles.Lookup lookup = MethodHandles.lookup();
      MethodHandle mh = lookup.findVirtual(HW.class, "hello1",
                                           MethodType.methodType(void.class));
      mh.invoke(hw);
      mh = lookup.findVirtual(HW.class, "hello2",
                              MethodType.methodType(void.class));
   }
}

Listing 2 declares HW (Hello, World) and MHD classes. HW declares a public hello1() instance method and a private hello2() instance method. MHD declares a main() method that will attempt to invoke these methods.

main()'s first task is to instantiate HW in preparation for invoking hello1() and hello2(). Next, it obtains a lookup object and uses this object to obtain a method handle for invoking hello1(). This time, MethodHandles.Lookup's findVirtual() method is called and the first argument passed to this method is a Class object describing the HW class.

It turns out that findVirtual() will succeed, and the subsequent mh.invoke(hw); expression will invoke hello1(), resulting in hello from hello1 being output.

Because hello1() is public, it's accessible to the main() method call site. In contrast, hello2() isn't accessible. As a result, the second findVirtual() invocation will fail with an IllegalAccessException.

When you run this application, you should observe the following output:

hello from hello1
Exception in thread "main" java.lang.IllegalAccessException: member is private: HW.hello2()void, from MHD
	at java.lang.invoke.MemberName.makeAccessException(MemberName.java:507)
	at java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:1172)
	at java.lang.invoke.MethodHandles$Lookup.checkMethod(MethodHandles.java:1152)
	at java.lang.invoke.MethodHandles$Lookup.accessVirtual(MethodHandles.java:648)
	at java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:641)
	at MHD.main(MHD.java:27)

Q: Listings 1 and 2 use the invokeExact() and invoke() methods to execute a method handle. What's the difference between these methods?

A: Although invokeExact() and invoke() are designed to execute a method handle (actually, the target code to which the method handle refers), they differ when it comes to performing type conversions on arguments and the return value. invokeExact() doesn't perform automatic compatible-type conversion on arguments. Its arguments (or argument expressions) must be an exact type match to the method signature, with each argument provided separately, or all arguments provided together as an array. invoke() requires its arguments (or argument expressions) to be a type-compatible match to the method signature -- automatic type conversions are performed, with each argument provided separately, or all arguments provided together as an array.

Q: Can you provide me with an example that shows how to invoke an instance field's getter and setter?

A: Check out Listing 3.

Listing 3. MHD.java (version 3)

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

class Point
{
   int x;
   int y;
}

public class MHD
{
   public static void main(String[] args) throws Throwable
   {
      MethodHandles.Lookup lookup = MethodHandles.lookup();

      Point point = new Point();

      // Set the x and y fields.

      MethodHandle mh = lookup.findSetter(Point.class, "x", 
                                          int.class);
      mh.invoke(point, 15);

      mh = lookup.findSetter(Point.class, "y", int.class);
      mh.invoke(point, 30);

      mh = lookup.findGetter(Point.class, "x", int.class);
      int x = (int) mh.invoke(point);
      System.out.printf("x = %d%n", x);

      mh = lookup.findGetter(Point.class, "y", int.class);
      int y = (int) mh.invoke(point);
      System.out.printf("y = %d%n", y);
   }
}

Listing 3 introduces a Point class with a pair of 32-bit integer instance fields named x and y. Each field's setter and getter is accessed by calling MethodHandles.Lookup's findSetter() and findGetter() methods, and the resulting MethodHandle is returned. Each of findSetter() and findGetter() requires a Class argument that identifies the field's class, the field's name, and a Class object that identifies the field's signature.

The invoke() method is used to execute a setter or getter-- behind the scenes, the instance fields are accessed via the JVM's putfield and getfield instructions. This method requires that a reference to the object whose field is being accessed be passed as the initial argument. For setter invocations, a second argument, consisting of the value being assigned to the field, also must be passed.

When you run this application, you should observe the following output:

x = 15
y = 30

Q: Your definition of method handle includes the phrase "with optional transformations of arguments or return values". Can you provide an example of argument transformation?

A: I've created an example based on the Math class's double pow(double a, double b) class method. In this example, I obtain a method handle to the pow() method, and transform this method handle so that the second argument passed to pow() is always 10. Check out Listing 4.

Related:
1 2 3 Page 1
Page 1 of 3
InfoWorld Technology of the Year Awards 2023. Now open for entries!