Нотация байт-кода java JVM, грамматика комментариев. InvokeДинамический

Вопрос: Что означает строка 14?

Используйте javap -v -c для дизассемблирования следующего кода:

 public class test {
     static int i = 2;
     public static void main(String[] args) {
         test x = new test();
         System.out.println("text + String: " + i);
     } 
 }

в основной функции получаем следующее:

14: invokedynamic #20,  0             // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
19: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
...
BootstrapMethods:
  0: #38 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #44 text + String: \u0001

Так, например, строка 19 означает, что вызывается виртуальная функция из элемента #24 в пуле констант времени выполнения. Вызванный метод — println() из класса java/io/PrintStream, его ввод — из класса Ljava/lang/String, возвращаемое значение — Void.

Что касается строки 14, #0 содержит ссылку на BootstrapMethod и возвращает объект, класс которого равен CallSite, верно? Затем:

  1. на что указывает №20?
  2. Что означает комментарий #0:makeConcatWithConstants:(I)Ljava/lang/String;?

Кроме того, где я могу узнать больше о грамматике кода дизассемблирования Javap? или какое правильное ключевое слово? Документ Oracle о the JVM instruction set, похоже, не дает четкого описания смысла комментария.


person chnnn    schedule 28.04.2020    source источник


Ответы (2)


Краткая версия: начиная с Java 9, Java использует invokedynamic для конкатенации строк.

Давайте немного разберем это:

Invokedynamic состоит из двух шагов:

  • Когда инструкция вызывается в первый раз, вызывается метод начальной загрузки. Когда он вернется, сайт вызова будет связан с результатом метода начальной загрузки.
  • Последующие вызовы будут напрямую вызывать цель MethodHandle.

CallSite является просто держателем этого MethodHandle. В зависимости от используемого подкласса CallSite сайт может быть позже перелинкован.

Если мы посмотрим на инструкцию, то увидим в конце следующее:

#0:makeConcatWithConstants:(I)Ljava/lang/String;

Первая часть (#0) означает: метод начальной загрузки № 0.
Вторая часть — это имя, которое передается методу начальной загрузки и может использоваться или не использоваться там.
Третья часть — это тип метода получившаяся цель. В нашем случае: метод, который принимает int и возвращает java.lang.String.

Если мы теперь посмотрим на метод начальной загрузки № 0, мы увидим ссылку на метод, здесь на StringConcatFactory.makeConcatWithConstants(...). Мы также видим, что есть дополнительный аргумент: Строка "text + String: \u0001".

Задача метода начальной загрузки теперь состоит в том, чтобы вернуть MethodHandle (внутри CallSite), который в данном случае выполняет эту конкатенацию строк. Но то, как он выполняет конкатенацию строк (StringBuilder, String.format, вращение байт-кода, объединение цепочек MethodHandles...), не имеет значения для фактического класса. Он хочет только конкатенировать строки.


Давайте попробуем сымитировать это поведение вручную. В конце концов, метод начальной загрузки — это обычный метод Java:

public static void main(String[] args) throws Throwable {
    CallSite cs = StringConcatFactory.makeConcatWithConstants(MethodHandles.lookup(),
            "makeConcatWithConstants", MethodType.methodType(String.class, int.class),
            "text + String: \u0001");

    int x = 2;
    String result = (String) cs.dynamicInvoker().invokeExact(x);
    System.out.println(result);

    x = 3;
    result = (String) cs.dynamicInvoker().invokeExact(x);
    System.out.println(result);
}

(Виртуальная машина делает еще кое-что, например запоминает результат и больше не будет вызывать метод начальной загрузки, но для нашего небольшого примера этого достаточно).


На этом этапе мы можем заглянуть под капот и понять, как метод начальной загрузки выполняет свою работу.
Оказывается: вы можете настроить виртуальную машину для использования различных стратегий.
И она использует свое привилегированное положение внутри java.base для доступа частный конструктор пакета для java.lang.String, который не копирует массив, что безопасно, если содержимое впоследствии не изменяется.

Стратегия по умолчанию — цепочка MethodHandle.

Хорошая новость: если кто-то когда-нибудь напишет лучшую стратегию, ваша программа выиграет от этого — без перекомпиляции.

person Johannes Kuhn    schedule 28.04.2020
comment
Привет, большое спасибо за очень подробный ответ и не только! Еще один вопрос: не могли бы вы рассказать немного больше об И он использует свое привилегированное положение внутри java.base для доступа к части частного конструктора пакета..., или какое ключевое слово я должен использовать для поиска дополнительной информации об этой цитате? - person chnnn; 29.04.2020
comment
Ах, да. java.lang.String имеет несколько приватных конструкторов пакетов. Не используйте их, но дизайн хорошо знать, если вы реализуете неизменяемые типы. - person Johannes Kuhn; 29.04.2020

См. спецификацию JVM< /а>:

Во-первых, беззнаковые indexbyte1 и indexbyte2 используются для построения индекса в пуле констант времени выполнения текущего класса (§2.6), ... Запись пула констант времени выполнения в индексе должна быть символической ссылкой на динамически -вычисленный сайт вызова (§5.1).

Для удобства javap уже просматривает пул констант и декодирует информацию; результат - это то, что было напечатано как комментарий за инструкцией в строке

14: invokedynamic #20,  0             // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;

Число #0 — это индекс атрибута BootstrapMethods, который вы уже опубликовали. Значение имени метода зависит от этого метода начальной загрузки. Далее идет дескриптор типа (I)Ljava/lang/String;, поэтому этот конкретный вызов использует int и создает String.

Что произойдет во время выполнения, зависит от указанного метода начальной загрузки. Этот вызов относится к методу static StringConcatFactory.makeConcatWithConstants(...) частично конкатенация строк компилируется с помощью Java 9.

документация этого метода сообщает Нам известно, что имя метода, используемое в инструкции invokedynamic, не имеет значения, а статический аргумент атрибута BootstrapMethod, т. е. text + String: \u0001, определяет формат строки. \u0001 заменяет «обычный аргумент», то есть параметр int.

person Holger    schedule 28.04.2020
comment
Привет, большое спасибо и признателен за четкий и четкий ответ со ссылкой. Я приму ответ Йоханнеса, так как он немного раньше и предложил дополнительное расширение, но этот ответ тоже отличный. Искренне благодарю и вас! - person chnnn; 29.04.2020