Статьи

Java 7: полный вызываемый динамический пример

Еще одна запись блога в моей текущей серии Java 7. На этот раз он имеет дело с invokedynamic, новой инструкцией байт-кода в JVM для вызова метода. Инструкция invokedynamic обеспечивает динамическую связь между сайтом вызова и получателем вызова.

Это означает, что вы можете связать класс, который выполняет вызов метода, с классом (и методом), который получает вызов во время выполнения . Все другие инструкции байт-кода 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 #200             // 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. Я надеюсь, что вам понравилось читать это — во времена нехватки!

Ура, Никлас

Ссылка: