The release of Java 7 contained several new features that seemed, at first sight, to be of limited use to Java developers, and we covered some of those in our article.
However, one feature in particular proved to be crucial in implementing the "headline" features of Java 8 (such as lambdas and default methods). In this article, we want to dive deeper on invokedynamic and explain why it is such a powerful tool for the Java platform, and for JVM languages such as JRuby and Nashorn.
The earliest work on invokedynamic goes back to at least 2007 with the first successful dynamic invocation taking place on August 26 2008. This predates Sun’s acquisition by Oracle and shows that this feature has been in progress for a long time, by the standards of most developers.
invokedynamic is remarkable in that it was the first new bytecode ever since Java 1.0. It joined the existing invoke bytecodes invokevirtual, invokestatic, invokeinterface and invokespecial. These four existing opcodes implement all forms of method dispatch that Java developers are typically familiar with, specifically:
- invokevirtual - the standard dispatch for instance methods
- invokestatic - used to dispatch static methods
- invokeinterface - used to dispatch a method call via an interface
- invokespecial - used when non-virtual (i.e. "exact") dispatch is required
Some developers may be curious as to why the platform requires all four opcodes, so let’s look at a simple example that uses the different invoke opcodes, to demonstrate the difference between them:
public class InvokeExamples {
public static void main(String[] args) {
InvokeExamples sc = new InvokeExamples();
sc.run();
}
private void run() {
List<String> ls = new ArrayList<>();
ls.add("Good Day");
ArrayList<String> als = new ArrayList<>();
als.add("Dydh Da");
}
}
This produces bytecode that we can disassemble with the javap tool:
javap -c InvokeExamples.class
public class kathik.InvokeExamples {
public kathik.InvokeExamples();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class kathik/InvokeExamples
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokespecial #4 // Method run:()V
12: return
private void run();
Code:
0: new #5 // class java/util/ArrayList
3: dup
4: invokespecial #6 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #7 // String Good Day
11: invokeinterface #8, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: new #5 // class java/util/ArrayList
20: dup
21: invokespecial #6 // Method java/util/ArrayList."<init>":()V
24: astore_2
25: aload_2
26: ldc #9 // String Dydh Da
28: invokevirtual #10 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
31: pop
32: return
}
This showcases three of the four invoke opcodes (and the remaining one, invokestatic is a fairly trivial extension). To start with, we can see that the two calls (at bytes 11 and 28 in the run method):
ls.add("Good Day")
and
als.add("Dydh Da")
look very similar in Java source code but are actually represented differently in bytecode.
To javac, the variable ls has the static type of List<String>
, and List is an interface. So the precise location in the runtime method table (usually called a "vtable") of the add() method has not yet been determined at compile time. The source code compiler therefore emits an invokeinterface instruction, and defers the actual lookup of the method until runtime, when the real vtable of ls can be inspected, and the location of the add() method found.
By contrast, the call als.add("Dydh Da")
is received by als, and the static type of this is a class type - ArrayList<String>
. This means that the location of the method in the vtable is known at compile time. javac is therefore able to emit an invokevirtual instruction for the exact vtable entry. The final choice of method while still be determined at runtime, because this allows for the possibility of method overriding, but the vtable slot has been determined at compile time.
Not only that, but the example also shows two of the possible use cases of invokespecial. This opcode is used in those cases where dispatch should be exactly determined at runtime, and in particular, method overriding is neither desired nor possible. The two cases the example demonstrates are private methods and super calls, because such methods are known at compile time and cannot be overridden.
The astute reader will have noticed that all calls to Java methods are compiled to one of these four opcodes so the question arises - what is invokedynamic for, and why is it useful to Java developers?
The features main goal was to create a bytecode to handle a new type of method dispatch - that essentially allows application-level code to determine which method a call will execute, and to do so only when the call is about to execute. This allows language and framework writers to support much more dynamic programming styles than the Java platform previously provided.
The intent is that user code determines dispatch at runtime using the method handles API whilst not suffering the performance penalties and security problems associated with reflection. In fact, the stated aim of invokedynamic is to be as fast as regular method dispatch (invokevirtual) once the feature has matured sufficiently.
When Java 7 arrived, the JVM had support for executing the new bytecode, but regardless of the Java code submitted, javac would never produce bytecode that included invokedynamic. Instead, the feature was used to support JRuby and other dynamic languages running on the JVM.
This changed in Java 8, where invokedynamic is now generated and used under the hood to implement lambda expressions and default methods, as well as the primary dispatch mechanism for Nashorn. However, there is still no direct way for Java application developers to do fully dynamic method resolution. That is, the Java language does not have a keyword or library that will create general-purpose invokedynamic call sites. This means that the mechanism remains obscure to most Java developers, despite the power it provides. Let’s see how we can leverage that in our code.
Introduction to Method Handles
For invokedynamic to work correctly, a key concept is the method handle. This is a way to represent the method that should be called from the invokedynamic call site. The general idea is that each invokedynamic instruction is associated with a special method (known as a bootstrap method or BSM). When the invokedynamic instruction is reached by the interpreter, the BSM is called. It returns an object (containing a method handle) that indicates which method the call site should actually execute.
This is somewhat similar to reflection, but reflection has limitations that made it unsuitable for use with invokedynamic. Instead, the java.lang.invoke.MethodHandle (and subclasses) were added to the Java 7 API, to represent methods that invokedynamic can target. The class MethodHandle receives some special treatment from the JVM, in order to operate correctly.
One way to think of method handles is as core reflection done in a safe, modern way with maximum possible type safety. They are needed for invokedynamic but can also be used standalone.
Method Types
A Java method can be considered to be made up of four basic pieces:
- Name
- Signature (including return type)
- Class where it is defined
- Bytecode that implements the method
This means that if we want to refer to methods we need a way of representing method signatures efficiently (rather than using the horrible Class<?>[] hacks that reflection is forced to use).
To put it another way, one of the first building blocks needed for method handles is a way of representing method signatures for lookup. In the Method Handles API, introduced in Java 7, this role is fulfilled by the java.lang.invoke.MethodType class, which uses immutable instances to represent signatures. To obtain a MethodType, use the methodType() factory method. This is a variadic method that takes class objects as arguments.
The first argument is the class object corresponding to the signature’s return type; the remaining arguments are the class objects that correspond to the types of the method arguments in the signature. For example:
// Signature of toString()
MethodType mtToString = MethodType.methodType(String.class);
// Signature of a setter method
MethodType mtSetter = MethodType.methodType(void.class, Object.class);
// Signature of compare() from Comparator<String>
MethodType mtStringComparator = MethodType.methodType(int.class, String.class, String.class);
Using the MethodType we can now use it, along with the name and the class that defines the method to lookup the method handle. To do this, we need to call the static MethodHandles.lookup() method. This gives us a "lookup context" that is based on the access rights of the currently executing method (i.e. the method that called lookup()).
The lookup context object has a number of methods that have names that start with "find", e.g. findVirtual(), findConstructor(), findStatic(). These methods will return the actual method handle, but only if the lookup context was created in a method that could access (call) the requested method. Unlike reflection, there is no way to subvert this access control. In other words, method handles have no equivalent of the setAccessible() method. For example:
public MethodHandle getToStringMH() {
MethodHandle mh = null;
MethodType mt = MethodType.methodType(String.class);
MethodHandles.Lookup lk = MethodHandles.lookup();
try {
mh = lk.findVirtual(getClass(), "toString", mt);
} catch (NoSuchMethodException | IllegalAccessException mhx) {
throw (AssertionError)new AssertionError().initCause(mhx);
}
return mh;
}
There are two methods on MethodHandle that can be used to invoke a method handle invoke() and +invokeExact(). Both methods take the receiver and call arguments as parameters, so the signatures are:
public final Object invoke(Object... args) throws Throwable;
public final Object invokeExact(Object... args) throws Throwable;
The difference between the two is that invokeExact() tries to call the method handle directly with the precise arguments provided. On the other hand, invoke() has the ability to slightly alter the method arguments if needed. invoke() performs an asType() conversion which can convert arguments according to this set of rules:
- Primitives will be boxed if required
- Boxed primitives will be unboxed if required
- Primitives will be widened if necessary
- A void return type will be converted to 0 (for primitive return types) or null for return types that expect a reference type
- null values are assumed to be correct and passed through regardless of static type
Let’s look at a simple invocation example that takes into account these rules:
Object rcvr = "a";
try {
MethodType mt = MethodType.methodType(int.class);
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle mh = l.findVirtual(rcvr.getClass(), "hashCode", mt);
int ret;
try {
ret = (int)mh.invoke(rcvr);
System.out.println(ret);
} catch (Throwable t) {
t.printStackTrace();
}
} catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
In more complex examples, method handles can provide a much clearer way to perform the same dynamic programming tasks as core reflection. Not only that, but method handles have been designed from the start to work better with the low-level execution model of the JVM and potentially can provide much better performance (although the performance story is still unfolding).
Method Handles and invokedynamic
The invokedynamic uses method handles via the bootstrap methods mechanism. Unlike invokevirtual, invokedynamic instructions have no receiver object. Instead, they act like invokestatic, and use a BSM to return an object of type CallSite. This object contains a method handle (called the "target") that represents the method that is to be executed as the result of the invokedynamic instruction.
When a class containing invokedynamic is loaded, the call sites are said to be in an "unlaced" state, and after the BSM returns, the resulting CallSite and method handle is said to be "laced" into the call site.
The signature for a BSM looks like this (note that the BSM can have any name):
static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type);
If we want to create code that actually contains invokedynamic, we will need to use a bytecode manipulation library (as the Java language doesn’t contain the constructs we need). In the rest of this article, we will need to use the ASM library to generate bytecode that includes invokedynamic instructions. From the point of view of Java applications, these appear as regular class files (although they, of course, have no possible Java source code representation). They are treated by Java code as "black boxes", that we can nonetheless call methods on and make use of invokedynamic and related functionality.
Let’s look at an ASM-based class for creating a "Hello World" using invokedynamic.
public class InvokeDynamicCreator {
public static void main(final String[] args) throws Exception {
final String outputClassName = "kathik/Dynamic";
try (FileOutputStream fos
= new FileOutputStream(new File("target/classes/" + outputClassName + ".class"))) {
fos.write(dump(outputClassName, "bootstrap", "()V"));
}
}
public static byte[] dump(String outputClassName, String bsmName, String targetMethodDescriptor)
throws Exception {
final ClassWriter cw = new ClassWriter(0);
MethodVisitor mv;
// Setup the basic metadata for the bootstrap class
cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, outputClassName, null, "java/lang/Object", null);
// Create a standard void constructor
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
// Create a standard main method
mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
mv.visitCode();
MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class);
Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "kathik/InvokeDynamicCreator", bsmName,
mt.toMethodDescriptorString());
mv.visitInvokeDynamicInsn("runDynamic", targetMethodDescriptor, bootstrap);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
private static void targetMethod() {
System.out.println("Hello World!");
}
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
// Need to use lookupClass() as this method is static
final Class<?> currentClass = lookup.lookupClass();
final MethodType targetSignature = MethodType.methodType(void.class);
final MethodHandle targetMH = lookup.findStatic(currentClass, "targetMethod", targetSignature);
return new ConstantCallSite(targetMH.asType(type));
}
}
The code is divided into two sections, the first of which uses the ASM Visitor API to create a class file called kathik.Dynamic. Note the key call to visitInvokeDynamicInsn(). The second section contains the target method that will be laced into the call site, and the BSM that the invokedynamic instruction needs.
Note that these methods are within the InvokeDynamicCreator class and not part of our generated class kathik.Dynamic. This means that at runtime InvokeDynamicCreator must also be on the classpath as well as kathik.Dynamic, or the method won’t be able to be found.
When InvokeDynamicCreator is run, it creates a new class file, Dynamic.class, which contains an invokedynamic instruction, as we can see by using javap on the class:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokedynamic #20, 0 // InvokeDynamic #0:runDynamic:()V
5: return
This example has shown the simplest case of invokedynamic, which uses the special case of a constant CallSite object. This means that the BSM (and lookup) is done only once and so subsequent calls are fast.
However, more sophisticated usages of invokedynamic can get complex quickly, especially when the call site and target method can change during the lifetime of the program.
In the next article, we will examine some more advanced use cases and build some examples, as well as dig deeper into the details of invokedynamic.
About the Author
Ben Evans is the CEO of jClarity, a Java/JVM performance analysis startup. In his spare time he is one of the leaders of the London Java Community and holds a seat on the Java Community Process Executive Committee. His previous projects include performance testing the Google IPO, financial trading systems, writing award-winning websites for some of the biggest films of the 90s, and others.