Это означает, что вы можете связать класс, который выполняет вызов метода, с классом (и методом), который получает вызов во время выполнения . Все другие инструкции байт-кода JVM для вызова метода, такие как invokevirtual , жестко связывают информацию о типе цели в вашей компиляции, то есть в файле класса. Давайте посмотрим на пример.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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; } |
Приведенный выше фрагмент байт-кода показывает вызов метода invokevirtual для java.lang. String -> length () в строке 20. Он ссылается на элемент 65 в таблице пула констант, который является записью MethodRef (см. Строку 6). Элементы 42 и 66 в таблице констант пула относятся к классу и записям дескриптора метода. Как видите, тип цели и метод вызова invokevirtual полностью разрешены и встроены в байт-код. Теперь вернемся к invokedynamic !
Важно отметить, что невозможно скомпилировать код Java в байт-код, который содержит вызванную динамическую инструкцию. Ява статически типизирована . Это означает, что Java выполняет проверку типов во время компиляции. Поэтому в Java можно (и нужно!) Жестко связать всю информацию о типах получателей вызова метода в файле класса вызывающих. Вызывающая сторона знает имя типа цели вызова, как показано в нашем примере выше. Использование invokedynamic, с другой стороны, позволяет JVM разрешать именно эту информацию о типе во время выполнения. Это требуется (и требуется!) Только для динамических языков, таких как JRuby или Rhino.
Теперь предположим, что вы хотите внедрить в JVM новый язык с динамической типизацией. Я не предлагаю вам изобретать * другой * язык в JVM, но * предположим, что вы это сделаете, и * предположим, что * ваш новый язык должен быть динамически типизирован. Это означает, что на вашем новом языке связь между вызывающим и получающим вызовом метода выполняется во время выполнения. Начиная с Java 7 это возможно на уровне байт-кода с помощью инструкции invokedynamic .
Поскольку я не могу создать инструкцию invokedynamic, используя компилятор Java, я создам файл класса, который содержит invokedynamic. Как только этот файл класса будет создан, я буду запускать основной метод этого файла класса, используя обычный модуль запуска Java. Как вы можете создать файл класса без компилятора? Это возможно с помощью каркасов манипулирования байт-кодом, таких как ASM или Javassist .
В следующем фрагменте кода показан SimpleDynamicInvokerGenerator, который может сгенерировать файл класса SimpleDynamicInvoker.class, который содержит вызываемую динамическую инструкцию.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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" )); } } |
Я использую ASM , универсальную среду манипулирования и анализа байт-кода Java, для создания правильного формата файла класса. В строке 30 visitInvokeDynamicInsn создает команду invokedynamic. Генерация класса, который выполняет вызываемый динамический вызов — это только половина истории. Вам также нужен код, который связывает сайт динамического вызова с реальной целью, это реальная цель invokedynamic. Вот пример.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
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 выбирает фактическую цель динамического вызова. В нашем случае целью является метод sayHello (). Чтобы узнать, как метод начальной загрузки связан с инструкцией invokedynamic, нам нужно погрузиться в байт-код SimpleDynamicInvoker, который мы сгенерировали с помощью SimpleDynamicInvokerGenerator .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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 } |
В строке 49 вы можете увидеть команду invokedynamic. Логическое имя динамического метода — runCalculation , это вымышленное имя. Вы можете использовать любое имя, которое имеет смысл, также допускаются имена типа «+». Инструкция относится к пункту 20 в постоянной таблице пула (см. Строку 33). Это, в свою очередь, относится к индексу 0 в атрибуте BootstrapMethods (см. Строку 8). Там вы можете увидеть ссылку на метод SimpleDynamicLinkageExample.bootstrapDynamic, который связывает инструкцию invokedynamic с целью вызова.
Теперь, если вы вызываете SimpleDynamicInvoker с помощью Java Launcher, то вызывается динамический вызов.
Следующая диаграмма последовательности иллюстрирует, что происходит, когда SimpleDynamicInvoker вызывается с помощью Java- лаунчера.
Первый вызов runCalculation с использованием invokedynamic вызывает вызов метода bootstrapDynamic . Этот метод выполняет динамическую связь между вызывающим классом (SimpleDynamicInvoker) и принимающим классом ( SimpleDynamicLinkageExample ). Метод начальной загрузки возвращает MethodHandle, который нацелен на принимающий класс. Этот дескриптор метода кэшируется для повторяющихся вызовов метода runCalculation .
Это все с точки зрения invokedynamic. У меня есть более сложные примеры, опубликованные здесь в моем репозитории Git. Я надеюсь, что вам понравилось читать это — во времена нехватки!
Ура, Никлас
Ссылка:
- «Java 7: полный пример динамической работы от нашего» партнера JCG Никлас.
- 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