Что такое invokedynamic и как мне его использовать?

Я продолжаю слышать обо всех новых интересных функциях, которые добавляются в JVM, и одна из этих интересных функций - динамическая. Я хотел бы знать, что это такое и как это делает рефлексивное программирование на Java легче или лучше?

5 ответов

Решение

Это новая инструкция JVM, которая позволяет компилятору генерировать код, который вызывает методы с более слабой спецификацией, чем это было возможно ранее - если вы знаете, что такое " типизация утки", invokedynamic в основном позволяет вводить утку. Вы не так уж много можете сделать, как программист на Java; если вы создаете инструмент, вы можете использовать его для создания более гибких и эффективных языков на основе JVM. Вот очень приятный пост в блоге, который дает много деталей.

В рамках своей статьи в Java Records я сформулировал мотивацию Inoke Dynamic. Начнем с грубого определения Инди.

Представляем Инди

Invoke Dynamic (также известный как Indy) был частью JSR 292, намереваясь улучшить поддержку JVM для языков динамического типа. После своего первого выпуска на Java 7, Theinvokedynamic код операции вместе с его java.lang.invoke Багаж довольно широко используется динамическими языками на основе JVM, такими как JRuby.

Хотя indy специально разработан для улучшения поддержки динамических языков, он предлагает гораздо больше. Фактически, его можно использовать везде, где разработчику языка требуется любая форма динамичности, от акробатики динамического типа до динамических стратегий!

Например, лямбда-выражения Java 8 фактически реализованы с использованиемinvokedynamic, хотя Java - язык со статической типизацией!

Байт-код, определяемый пользователем

Некоторое время JVM действительно поддерживала четыре типа вызова методов: invokestatic для вызова статических методов, invokeinterface для вызова методов интерфейса, invokespecial вызывать конструкторы, super() или частные методы и invokevirtual для вызова методов экземпляра.

Несмотря на различия, эти типы вызовов имеют одну общую черту: мы не можем обогатить их нашей собственной логикой. Наоборот,invokedynamic позволяет нам запускать процесс вызова любым способом. Затем JVM позаботится о прямом вызове метода начальной загрузки.

Как работает Indy?

Когда JVM впервые видит invokedynamicинструкция, он вызывает специальный статический метод, называемый Bootstrap Method. Метод начальной загрузки - это фрагмент кода Java, который мы написали для подготовки фактической вызываемой логики:

Затем метод начальной загрузки возвращает экземпляр java.lang.invoke.CallSite. ЭтотCallSite содержит ссылку на фактический метод, т.е. MethodHandle.

С этого момента каждый раз, когда JVM видит это invokedynamicИ снова, она пропускает медленный путь и напрямую вызывает исполняемый файл. JVM продолжает пропускать медленный путь, если что-то не изменится.

Пример: Java 14 Records

Java 14 Records предоставляют удобный компактный синтаксис для объявления классов, которые должны быть тупыми держателями данных.

Учитывая эту простую запись:

public record Range(int min, int max) {}

Байт-код для этого примера будет примерно таким:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

В таблице методов начальной загрузки:

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

Итак, метод начальной загрузки для записей называетсяbootstrap который находится в java.lang.runtime.ObjectMethodsкласс. Как видите, этот метод начальной загрузки ожидает следующие параметры:

  • Пример MethodHandles.Lookup представляющий контекст поиска (Ljava/lang/invoke/MethodHandles$Lookup часть).
  • Имя метода (т.е. toString, equals, hashCodeи т. д.) загрузчик будет ссылаться. Например, когда значениеtoString, бутстрап вернет ConstantCallSiteCallSite который никогда не меняется), который указывает на действительный toString реализация для этой конкретной записи.
  • В TypeDescriptor для метода (Ljava/lang/invoke/TypeDescriptor часть).
  • Типовой токен, т.е. Class<?>, представляющий тип класса Record. Это Class<Range> в этом случае.
  • Список всех имен компонентов, разделенных точкой с запятой, т.е. min;max.
  • Один MethodHandleна компонент. Таким образом, метод начальной загрузки может создатьMethodHandle на основе компонентов для реализации этого конкретного метода.

В invokedynamicИнструкция передает все эти аргументы методу начальной загрузки. Метод Bootstrap, в свою очередь, возвращает экземплярConstantCallSite. ЭтотConstantCallSite содержит ссылку на запрошенную реализацию метода, например toString.

Почему Инди?

В отличие от API отражения, java.lang.invokeAPI довольно эффективен, поскольку JVM может полностью видеть все вызовы. Следовательно, JVM может применять всевозможные оптимизации, если мы максимально избегаем медленного пути!

В дополнение к аргументу эффективности invokedynamicподход более надежен и менее хрупок в силу своей простоты.

Более того, сгенерированный байт-код для записей Java не зависит от количества свойств. Таким образом, меньше байт-кода и более быстрое время запуска.

Наконец, давайте предположим, что новая версия Java включает новую и более эффективную реализацию метода начальной загрузки. С участиемinvokedynamic, наше приложение может воспользоваться этим улучшением без перекомпиляции. Таким образом, у нас есть своего рода прямая двоичная совместимость. Кроме того, мы говорили о динамической стратегии!

Другие примеры

Помимо записей Java, динамический вызов используется для реализации таких функций, как:

Некоторое время назад в C# добавлена ​​классная функция, динамический синтаксис в C#

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Думайте об этом как о синтаксическом сахаре для рефлексивных вызовов методов. У него могут быть очень интересные приложения. см. http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Нил Гафтер, ответственный за динамический тип C#, только что перешел с SUN на MS. Так что не исключено, что в SUN обсуждались те же самые вещи.

Я помню, вскоре после этого какой-то Java чувак объявил что-то подобное

InvokeDynamic duck = obj;
duck.quack(); 

К сожалению, эта функция отсутствует в Java 7. Очень разочарован. Для Java-программистов у них нет простого способа воспользоваться invokedynamic в своих программах.

Прежде чем переходить к invokedynamic, необходимо понять две концепции.

1. Статический и динамический ввод

Статический - проверка типа преформ во время компиляции (например, Java)

Динамический - проверка типа преформ во время выполнения (например, JavaScript)

Проверка типов - это процесс проверки типобезопасности программы, то есть проверка типизированной информации для переменных класса и экземпляра, параметров метода, возвращаемых значений и других переменных. Например, Java знает о int, String,.. во время компиляции, а тип объекта в JavaScript может быть определен только во время выполнения.

2. Сильная и слабая типизация

Strong - указывает ограничения на типы значений, передаваемых его операциям (например, Java)

Слабый - преобразует (приводит) аргументы операции, если эти аргументы имеют несовместимые типы (например, Visual Basic).

Зная, что Java является статически и слабо типизированной, как вы реализуете языки с динамической и строгой типизацией на JVM?

Invokedynamic реализует систему времени выполнения, которая может выбрать наиболее подходящую реализацию метода или функции - после того, как программа была скомпилирована.

Пример: имея (a + b) и ничего не зная о переменных a, b во время компиляции, invokedynamic сопоставляет эту операцию с наиболее подходящим методом в Java во время выполнения. Например, если окажется, что a, b - строки, вызовите метод (String a, String b). Если оказывается, что a, b - целые числа, тогда вызовите метод (int a, int b).

invokedynamic был представлен в Java 7.

Короткий ответ: invokedynamic — это новый код операции в JVM, которого не было до JAVA 7.

Что касается отражения, в контексте этого определения: Java Reflection — это процесс изучения или изменения поведения класса во время выполнения во время выполнения. , однако, я считаю, что необходимо больше пояснений. Из ниже:

Например, отражение предшествует как коллекциям, так и дженерикам. В результате сигнатуры методов представлены Class[] в API Reflection. Это может быть громоздким и подверженным ошибкам, и этому препятствует многословный характер синтаксиса массивов Java. Кроме того, это усложняется необходимостью вручную упаковывать и распаковывать примитивные типы, а также обходить возможность использования методов void.

Ручки методов на помощь

Вместо того, чтобы заставлять программиста решать эти проблемы, в Java 7 появился новый API, названный MethodHandles, для представления необходимых абстракций. Ядром этого API является пакет java.lang.invoke и особенно класс MethodHandle. Экземпляры этого типа предоставляют возможность вызова метода и являются непосредственно исполняемыми. Они динамически типизируются в соответствии с их типами параметров и возвращаемых значений, что обеспечивает максимально возможную безопасность типов, учитывая динамический способ их использования. API необходим для invokedynamic, но его также можно использовать отдельно, и в этом случае его можно считать современной, безопасной альтернативой отражению.

Цитата из статьиПонимание вызова метода Java с помощью invokedynamic

Эти четыре являются представлениями байт-кода стандартных форм вызова методов, используемых в Java 8 и Java 9, и это - invokevirtual, invokespecial, invokeinterface и invokestatic.

Это поднимает вопрос о том, как пятый код операции, invokedynamic , входит в картину. Короткий ответ заключается в том, что начиная с Java 9 не было прямой поддержки invokedynamic в языке Java.

На самом деле, когда invokedynamic был добавлен в среду выполнения в Java 7, компилятор javac ни при каких обстоятельствах не выдавал новый байт-код.

Начиная с Java 8, invokedynamic используется в качестве основного механизма реализации для предоставления расширенных функций платформы. Один из самых ярких и простых примеров такого использования кода операции — реализация лямбда-выражений.

Итак, еще раз, invokedynamic — это новый код операции, который позволяет использовать новый тип ссылки на объект в JAVA, Lambda.

Другие вопросы по тегам