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 invokestatic
methods.invokevirtual
is used to invokepublic
andprotected
non-static
methods via dynamic dispatch.invokeinterface
is similar toinvokevirtual
except for the method dispatch being based on an interface type.invokespecial
is used to invoke instance initialization methods (constructors) as well asprivate
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.