這意味著您可以將正在執行方法調用的類鏈接到在運行時正在接收調用的類(和方法)。 所有其他用于方法調用的JVM字節碼指令,例如invokevirtual ,都將目標類型信息硬連線到編譯中,即硬連線到類文件中。 讓我們看一個例子。
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_PUBLICCode:stack=1, locals=1, args_size=10: ldc #44 // String Hello2: invokevirtual #65 // Method java/lang/String.length:()I5: pop6: returnLineNumberTable:line 31: 0line 32: 6LocalVariableTable:Start Length Slot Name Signature0 7 0 this Lcom/schlimm/bytecode/examples/BytecodeExamples;
}
上面的字節碼片段顯示了java.lang的invokevirtual方法調用。 第20行中的String- > length() 。它引用了contsant池表中的Item 65,它是MethodRef條目(請參見第6行)。 常量池表中的項目42和66引用類和方法描述符條目。 如您所見,invokevirtual調用的目標類型和方法已完全解析,并硬連接到字節碼中。 現在,讓我們回到invokedynamic !
重要的是要注意,不可能將Java代碼編譯為包含invokedynamic指令的字節碼。 Java是靜態類型的 。 這意味著Java在編譯時執行類型檢查。 因此,在Java中,有可能(并且想要!)將方法調用接收者的所有類型信息硬連線到調用者類文件中。 調用方知道調用目標的類型名稱,如上面的示例所示。 另一方面,使用invokedynamic可使JVM在運行時準確解析該類型信息。 只有動態語言(例如JRuby或Rhino)才需要(也想要!)。
現在,假設您要在動態鍵入的JVM上實現一種新語言。 我不建議您在JVM上發明另一種語言,但是假設您應該,并且假設您的新語言應被動態鍵入。 用您的新語言,這意味著在運行時執行方法調用的調用方和接收方之間的鏈接。 由于Java 7,這可以使用invokedynamic指令在字節碼級別上實現。
因為無法使用Java編譯器創建invokedynamic指令,所以我將創建一個包含我自己的invokedynamic的類文件。 創建此類文件后,我將使用普通的Java啟動器運行該類文件的main方法。 沒有編譯器,如何創建類文件? 這可以通過使用字節碼操作框架(例如ASM或Javassist)來實現 。
以下代碼段顯示了SimpleDynamicInvokerGenerator ,該生成器可以生成一個類文件SimpleDynamicInvoker.class,該文件包含invokedynamic指令。
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 {@Overrideprotected 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"));}}
我在這里使用ASM (一種通用的Java字節碼操縱和分析框架)來完成創建正確的類文件格式的工作。 在第30行中, visitInvokeDynamicInsn創建了invokedynamic指令。 生成一個進行invokedynamic調用的類只是故事的一半。 您還需要一些將動態調用站點鏈接到實際目標的代碼,這是invokedynamic的真正目的。 這是一個例子。
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));}}
第9-14行中的bootstrap方法選擇動態調用的實際目標。 在我們的例子中,目標是sayHello ()方法。 要了解bootstrap方法如何鏈接到invokedynamic指令,我們需要深入研究使用SimpleDynamicInvokerGenerator生成的SimpleDynamicInvoker字節碼。
E:\dev_home\repositories\git\playground\bytecode-playground\target\classes\com\schlimm\bytecode>javap -c -verbose SimpleDynamicInvoker.classClassfile /E:/dev_home/repositories/git/playground/bytecode-playground/target/classes/com/schlimm/bytecode/SimpleDynamicInvoker.classLast modified 30.01.2012; size 512 bytesMD5 checksum 401a0604146e2e95f9563e7d9f9d861b
public class com.schlimm.bytecode.SimpleDynamicInvokerBootstrapMethods: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: 0major version: 51flags: 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_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #8 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);flags: ACC_PUBLIC, ACC_STATICCode:stack=0, locals=1, args_size=10: invokedynamic #20, 0 // InvokeDynamic #0:runCalculation:()V5: return
}
在第49行中,您可以看到invokedynamic指令。 動態方法的邏輯名稱是runCalculation ,這是一個虛擬名稱。 您可以使用任何有意義的名稱,也可以使用“ +”之類的名稱。 該指令引用了競爭池表中的第20項(請參見第33行)。 這又引用了BootstrapMethods屬性中的索引0(請參見第8行)。 在這里,您可以看到指向SimpleDynamicLinkageExample.bootstrapDynamic方法的鏈接,該方法將invokedynamic指令鏈接到調用目標。
現在,如果您使用java啟動器調用SimpleDynamicInvoker ,則將執行invokedynamic調用。

下面的序列圖說明了使用Java啟動器調用SimpleDynamicInvoker時發生的情況。
使用invokedynamic的runCalculation的第一次調用會發出對bootstrapDynamic方法的調用。 此方法在調用類(SimpleDynamicInvoker)和接收類( SimpleDynamicLinkageExample )之間進行動態鏈接。 bootstrap方法返回一個以接收類為目標的MethodHandle。 緩存此方法句柄以重復調用runCalculation方法。
這就是invokedynamic。 我在Git倉庫中發布了一些更復雜的示例。 希望您在閱讀不足時喜歡閱讀本文!
干杯,尼克拉斯
參考:
- JCG合作伙伴提供的 “ Java 7:一個完整??的invokedynamic示例” ? 尼克拉斯。
- 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
翻譯自: https://www.javacodegeeks.com/2012/02/java-7-complete-invokedynamic-example.html