Почему Java 8 lambdas вызывается с помощью invokedynamic?

invokedynamic команда используется, чтобы помочь VM определить ссылку на метод во время выполнения, а не связать ее во время компиляции.

Это полезно для динамических языков, где точный метод и типы аргументов неизвестны до выполнения. Но это не относится к Java lambdas. Они переводятся в статический метод с четко определенными аргументами. И этот метод можно вызвать с помощью invokestatic .

Итак, зачем нужна invokedynamic для lambda, особенно когда есть удар производительности?

Lambdas не вызывается с использованием invokedynamic , их представление объектов создается с использованием invokedynamic , фактический вызов является регулярным invokevirtual или invokeinterface .

Например:

 // creates an instance of (a subclass of) Consumer // with invokedynamic to java.lang.invoke.LambdaMetafactory something(x -> System.out.println(x)); void something(Consumer consumer) { // invokeinterface consumer.accept("hello"); } 

Любая lambda должна стать экземпляром некоторого базового classа или интерфейса. Этот экземпляр иногда содержит копию переменных, взятых из исходного метода, и иногда указатель на родительский объект. Это может быть реализовано как анонимный class.

Почему invokedynamic

Короткий ответ: генерировать код во время выполнения.

Составители Java решили сгенерировать class реализации во время выполнения. Это делается путем вызова java.lang.invoke.LambdaMetafactory.metafactory . Поскольку аргументы для этого вызова (тип возврата, интерфейс и захваченные параметры) могут измениться, для этого требуется invokedynamic .

Использование invokedynamic для создания анонимного classа во время выполнения позволяет JVM генерировать этот байт-код classа во время выполнения. Последующие обращения к одному и тому же заявлению используют кешированную версию. Другой причиной использования invokedynamic является возможность изменения страtagsи реализации в будущем без необходимости изменения уже скомпилированного кода.

Дорога не сделана

Другим вариантом будет компилятор, создающий внутренний class для каждого экземпляра lambda, что эквивалентно переводу вышеуказанного кода на:

 something(new Consumer() { public void accept(x) { // call to a generated method in the base class ImplementingClass.this.lambda$1(x); // or repeating the code (awful as it would require generating accesors): System.out.println(x); } ); 

Для этого требуется создать classы во время компиляции и загрузить их во время выполнения. Как работает jvm, эти classы будут находиться в том же каталоге, что и исходный class. И в первый раз, когда вы выполняете оператор, который использует эту лямбду, этот анонимный class должен быть загружен и инициализирован.

О производительности

Первый вызов invokedynamic приведет к генерации анонимного classа. Затем код операции invokedynamic заменяется кодом, который эквивалентен по производительности для ручной записи анонимного экземпляра.

Мозг Гетц объяснил причины страtagsи перевода lambda в одной из своих работ, которые, к сожалению, сейчас, похоже, недоступны. К счастью, я сохранил копию:

Страtagsя перевода

Существует несколько способов представления lambda-выражения в байт-коде, таких как внутренние classы, обработчики методов, динамические прокси и другие. Каждый из этих подходов имеет свои плюсы и минусы. При выборе страtagsи существуют две конкурирующие цели: максимизация гибкости для будущей оптимизации, не привязка к конкретной страtagsи, а также обеспечение стабильности в представлении classfile. Мы можем достичь обеих этих целей, используя функцию invokedynamic из JSR 292, чтобы отделить двоичное представление создания lambda в байткоде от механики оценки lambda-выражения во время выполнения. Вместо генерации байт-кода для создания объекта, реализующего lambda-выражение (например, вызов конструктора для внутреннего classа), мы описываем рецепт построения lambda и делегируем фактическую конструкцию языку выполнения. Этот рецепт кодируется в статических и динамических списках аргументов invokedynamic.

Использование invokedynamic позволяет отложить выбор страtagsи перевода до времени выполнения. Реализация во время выполнения может свободно выбирать страtagsю для оценки выражения lambda. Выбор реализации времени выполнения скрыт за стандартизованным (то есть частью спецификации платформы) API для построения lambda, поэтому статический компилятор может вызывать вызовы для этого API, а реализации JRE могут выбрать предпочтительную страtagsю реализации. Механизм invokedynamic позволяет сделать это без затрат на производительность, которые этот метод позднего связывания мог бы наложить иначе.

Когда компилятор встречает lambda-выражение, он сначала понижает (desugars) тело лямбды в метод, список аргументов и тип возвращаемого значения которого соответствуют выражению lambda, возможно, с некоторыми дополнительными аргументами (для значений, взятых из лексической области, если они есть. ) В момент захвата lambda-выражения он генерирует вызываемый динамический сайт вызова, который при вызове возвращает экземпляр функционального интерфейса, к которому преобразуется lambda. Этот сайт вызова называется lambda-фабрикой для данной lambda. Динамические аргументы для lambda-фабрики – это значения, взятые из лексической области. Метод начальной загрузки lambda-фабрики является стандартизованным методом в библиотеке времени выполнения Java-языка, называемой lambda-метафайлой. Статические аргументы бутстрапа захватывают информацию, известную о lambda во время компиляции (функциональный интерфейс, к которому он будет преобразован, дескриптор метода для десубранного тела лямбды, информация о том, является ли тип SAM сериализуемым и т. Д.),

Ссылки на методы обрабатываются так же, как и lambda-выражения, за исключением того, что большинство ссылок на методы не нужно отбрасывать в новый метод; мы можем просто загрузить дескриптор константного метода для ссылочного метода и передать его метафайлу.

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

Текущая реализация lambda Java 8 – сложное решение:

    1. Скомбинировать lambda-выражение статическому методу в classе-оболочке; вместо компиляции lambdas для разделения внутренних файлов classов (Scala компилируется таким образом, поэтому много файлов $$$ class вокруг)
    1. Ввести постоянный пул: BootstrapMethods, который переносит вызов статического метода на объект callsite (может быть кэширован для последующего использования)

Поэтому, чтобы ответить на ваш вопрос,

    1. текущая реализация lambda с использованием invokedynamic немного быстрее, чем отдельный внутренний class, потому что не нужно загружать эти внутренние файлы classов, а вместо этого создавать байт внутреннего classа [] на лету (чтобы удовлетворить, например, интерфейс Function), и кэшированный для последующего использования.
    1. Команда JVM все еще может генерировать отдельный внутренний class (со ссылкой на статические методы в classе), он гибкий
  • Что такое lambda (функция)?
  • Java 8 Streams: несколько фильтров или сложное условие
  • Отличный () с лямбдой?
  • Использовать ссылку на метод с параметром
  • Как проверить нули в глубоком lambda-выражении?
  • C #: Получение имен свойств в цепочке из выражения lambda
  • java.util.stream с ResultSet
  • Почему для var нельзя назначить анонимный метод?
  • Лямбда-захват как const-ссылка?
  • Java 8: Разница между ссылкой метода Bound Receiver и UnBound Receiver
  • Справочник по методу экземпляра и lambda-параметры
  • Interesting Posts

    Не удалось вычислить план сборки: плагин org.apache.maven.plugins: maven-resources-plugin: 2.5 или одна из его зависимостей не может быть решена

    Как обрезать пробел из строки?

    Почему я получаю «Исключение; должен быть пойман или объявлен брошенным “, когда я пытаюсь скомпилировать свой Java-код?

    Звук, воспроизводимый через динамики, когда наушники подключены

    Как отключить foobar2000, когда Windows заблокирована?

    gdb не работает с ошибкой «Не удалось найти Mach task port for process-id»

    Как использовать Shell32 в приложении C #?

    Отображение Hibernate / JPA приводит к возникающим в JSF причинам: java.lang.NumberFormatException: для строки ввода: “”

    Бросил мой Mac на пол. Жесткий диск не работает

    У java действительно есть указатели или нет?

    Как получить общее время звукового файла в Java?

    Зачем нужна маска подсети?

    Битфилд-манипуляция в C

    Как сделать объемный микшер Windows 7 снова сохранить отдельные тома?

    Как заставить панель действий быть внизу в ICS?

    Давайте будем гением компьютера.