Java 7: A complete invokedynamic example
That means you can link the class that is performing a method call to the class (and method) that is receiving the call at run-time. All the other JVM bytecode instructions for method invocation, like invokevirtual, hard-wire the target type information into your compilation, i.e. into your class file. Let’s look at an example.
Constant pool: #1 = Class #2 // com/schlimm/bytecode/examples/BytecodeExamples ... #42 = Class #43 // java/lang/String ... #65 = Methodref #42.#66 // java/lang/String.length:()I #66 = NameAndType #67:#68 // length:()I #67 = Utf8 length #68 = Utf8 ()I ... { ... public void virtualMethodCall(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: ldc #44 // String Hello 2: invokevirtual #65 // Method java/lang/String.length:()I 5: pop 6: return LineNumberTable: line 31: 0 line 32: 6 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/schlimm/bytecode/examples/BytecodeExamples; }
The bytecode snippet above shows an invokevirtual method call of java.lang.String -> length() in line 20. It refers to item 65 in the contsant pool table which is a MethodRef entry (see line 6). Items 42 and 66 in the constant pool table refer to the class and the method descriptor entries. As you can see, the target type and method of the invokevirtual call is completely resolved and hard-wired into the bytecode. Now, let’s return to invokedynamic!
It is important to notice that it is not possible to compile Java code into bytecode that contains an invokedynamic instruction. Java is statically typed. That means that Java performs type checking at compile time. Therefore, in Java, it is possible (and wanted!) to hard-wire all type information of method call receivers into the callers class file. The caller knows the type name of the call target, as demonstrated in our example above. The use of invokedynamic – on the other hand – enables the JVM to resolve exactly that type information at run-time. This is only required (and wanted!) for dynamic languages, such as JRuby or Rhino.
Now, suppose you want to implement a new language on the JVM that is dynamically typed. I am not suggesting you should invent *another* language on the JVM, but *suppose* you would, and *suppose* your new language should be dynamically typed. That would mean, in your new language, the linking between a caller and a receiver of a method call is performed at run-time. Since Java 7 this is possible on the bytecode level using the invokedynamic instruction.
Because I cannot create an invokedynamic instruction using a Java compiler, I will create a class file that contains invokedynamic myself. Once this class file is created I will run that class file’s main method using an ordinary java launcher. How can you create a class file without a compiler? This is possible by using bytecode manipulation frameworks like ASM or Javassist.
The following code snippet shows the SimpleDynamicInvokerGenerator that can generate a class file SimpleDynamicInvoker.class which contains an invokedynamic instruction.
public abstract class AbstractDynamicInvokerGenerator implements Opcodes { public byte[] dump(String dynamicInvokerClassName, String dynamicLinkageClassName, String bootstrapMethodName, String targetMethodDescriptor) throws Exception { ClassWriter cw = new ClassWriter(0); FieldVisitor fv; MethodVisitor mv; AnnotationVisitor av0; cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, dynamicInvokerClassName, null, "java/lang/Object", null); { 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(); } { 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, dynamicLinkageClassName, bootstrapMethodName, mt.toMethodDescriptorString()); int maxStackSize = addMethodParameters(mv); mv.visitInvokeDynamicInsn("runCalculation", targetMethodDescriptor, bootstrap); mv.visitInsn(RETURN); mv.visitMaxs(maxStackSize, 1); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } protected abstract int addMethodParameters(MethodVisitor mv); } public class SimpleDynamicInvokerGenerator extends AbstractDynamicInvokerGenerator { @Override protected int addMethodParameters(MethodVisitor mv) { return 0; } public static void main(String[] args) throws IOException, Exception { String dynamicInvokerClassName = "com/schlimm/bytecode/SimpleDynamicInvoker"; FileOutputStream fos = new FileOutputStream(new File("target/classes/" + dynamicInvokerClassName + ".class")); fos.write(new SimpleDynamicInvokerGenerator().dump(dynamicInvokerClassName, "com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample", "bootstrapDynamic", "()V")); } }
I am using ASM here, an all purpose Java bytecode manipulation and analysis framework, to do the job of creating a correct class file format. In line 30 the visitInvokeDynamicInsn creates the invokedynamic instruction. Generating a class that does an invokedynamic call is only half of the story. You also need some code that links the dynamic call site to the actual target, this is the real purpose of invokedynamic. Here is an example.
public class SimpleDynamicLinkageExample { private static MethodHandle sayHello; private static void sayHello() { System.out.println("There we go!"); } public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); Class thisClass = lookup.lookupClass(); // (who am I?) sayHello = lookup.findStatic(thisClass, "sayHello", MethodType.methodType(void.class)); return new ConstantCallSite(sayHello.asType(type)); } }
The bootstrap method in line 9-14 selects the actual target of the dynamic call. In our case the target is the sayHello() method. To learn how the bootstrap method is linked to the invokedynamic instruction we need to dive into the bytecode of SimpleDynamicInvoker that we’ve generated with SimpleDynamicInvokerGenerator.
E:\dev_home\repositories\git\playground\bytecode-playground\target\classes\com\schlimm\bytecode>javap -c -verbose SimpleDynamicInvoker.class Classfile /E:/dev_home/repositories/git/playground/bytecode-playground/target/classes/com/schlimm/bytecode/SimpleDynamicInvoker.class Last modified 30.01.2012; size 512 bytes MD5 checksum 401a0604146e2e95f9563e7d9f9d861b public class com.schlimm.bytecode.SimpleDynamicInvoker BootstrapMethods: 0: #17 invokestatic com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Utf8 com/schlimm/bytecode/SimpleDynamicInvoker #2 = Class #1 // com/schlimm/bytecode/SimpleDynamicInvoker #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = NameAndType #5:#6 // "<init>":()V #8 = Methodref #4.#7 // java/lang/Object."<init>":()V #9 = Utf8 main #10 = Utf8 ([Ljava/lang/String;)V #11 = Utf8 com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample #12 = Class #11 // com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample #13 = Utf8 bootstrapDynamic #14 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #15 = NameAndType #13:#14 // bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #16 = Methodref #12.#15 // com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #17 = MethodHandle #6:#16 // invokestatic com/schlimm/bytecode/invokedynamic/linkageclasses/SimpleDynamicLinkageExample.bootstrapDynamic:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #18 = Utf8 runCalculation #19 = NameAndType #18:#6 // runCalculation:()V #20 = InvokeDynamic #0:#19 // #0:runCalculation:()V #21 = Utf8 Code #22 = Utf8 BootstrapMethods { public com.schlimm.bytecode.SimpleDynamicInvoker(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=0, locals=1, args_size=1 0: invokedynamic #20, 0 // InvokeDynamic #0:runCalculation:()V 5: return }
In line 49 you can see the invokedynamic instruction. The logical name of the dynamic method is runCalculation, this is a fictitious name. You can use any name that makes sense, also names like “+” are allowed. The instruction refers to item 20 in the contant pool table (see line 33). This in turn refers to index 0 in the BootstrapMethods attribute (see line 8). There you can see the link to the SimpleDynamicLinkageExample.bootstrapDynamic method that links the invokedynamic instruction to the call target.
Now if you call the SimpleDynamicInvoker using the java launcher, then the invokedynamic call is executed.
The following sequence diagram illustrates what’s happening when the SimpleDynamicInvoker is called using the java launcher.
The first call of runCalculation using invokedynamic issues a call to the bootstrapDynamic method. This method does the dynamic linkage between the calling class (SimpleDynamicInvoker) and the receiving class (SimpleDynamicLinkageExample). The bootstrap method returns a MethodHandle that targets the receiving class. This method handle is cached for repetitive invocations of the runCalculation method.
That’s all in terms of invokedynamic. I have some more sophisticated examples published here in my Git repo. I hope you’ve enjoyed reading this – in times of shortage!
Cheers, Niklas
Reference:
- “Java 7: A complete invokedynamic example from our” JCG partner Niklas.
- http://docs.oracle.com/javase/7/docs/technotes/guides/vm/multiple-language-support.html
- http://asm.ow2.org/
- http://java.sun.com/developer/technicalArticles/DynTypeLang/
- http://asm.ow2.org/doc/tutorial-asm-2.0.html
- http://weblogs.java.net/blog/forax/archive/2011/01/07/calling-invokedynamic-java
- http://nerds-central.blogspot.com/2011/05/performing-dynamicinvoke-from-java-step.html