Что делает инструкция `invokedynamic` в JVM?

Ответ

invokedynamic (индекс в байт-коде: 186 / 0xBA) — это байт-код инструкция JVM, введённая в Java 7 для поддержки динамически типизированных языков (JRuby, Groovy) на платформе JVM. В Java она стала ключевой для реализации лямбда-выражений и ссылок на методы.

Основная идея: Откладывает разрешение вызываемого метода от этапа компиляции (как invokevirtual или invokestatic) на этап первого выполнения (первая ссылка на call site). Это позволяет гибко менять логику вызова.

Как работает:

  1. При первом вызове invokedynamic JVM вызывает специальную метод-фабрику (bootstrap method).
  2. Эта фабрика возвращает java.lang.invoke.CallSite, который содержит ссылку на конечный исполняемый метод (MethodHandle).
  3. Последующие вызовы используют уже связанный CallSite, что делает их почти такими же быстрыми, как обычные виртуальные вызовы.

Пример для лямбды в Java:

// Исходный код
Function<String, Integer> func = s -> s.length();
// Примерный байт-код (декомпилированный)
invokedynamic #0:apply:()Ljava/util/function/Function;

Зачем это нужно Java:

  • Лямбда-выражения: Компилятор генерирует одну статическую реализацию лямбды и использует invokedynamic для её связывания, избегая создания множества анонимных классов.
  • Строки в switch (Java 7+): Использует invokedynamic для эффективного хеширования строк.
  • Конкатенация строк (Java 9+): Заменяет StringBuilder на стратегию, выбираемую во время выполнения, что повышает производительность.

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

Ответ 18+ 🔞

Слушай, а вот эта штука invokedynamic — это же, блядь, просто песня! Индекс у неё в байт-коде 186, или 0xBA, если ты любишь шестнадцатеричный вид. Завезли её в Java 7, типа, для поддержки всяких динамических языков вроде JRuby, которые на JVM бегают. А потом оказалось, что эта фигня — просто золотая жила для самих ява-разработчиков, когда лямбды и ссылки на методы делать стали.

Суть-то в чём, блядь? Вся соль в том, чтобы не решать, какой метод вызывать, в момент компиляции — как это делают старые добрые invokevirtual или invokestatic. Вместо этого всё откладывается до первого запуска, до первой встречи с этим местом вызова (call site). И это, сука, даёт нерестанную гибкость — логику вызова можно менять на лету!

Как этот цирк работает, ёпта:

  1. Первый раз, когда JVM натыкается на invokedynamic, она не ебёт мозг, а просто зовёт специальную загрузочную метод-фабрику (bootstrap method).
  2. Эта фабрика, подумав, выдаёт в ответ объект java.lang.invoke.CallSite. А внутри него уже лежит готовая ссылка на реальный метод, который надо выполнить (MethodHandle).
  3. Все последующие вызовы в этом месте уже летят прямиком по готовой ссылке из CallSite. И по скорости это, блядь, почти не отличимо от обычного вызова! Хитро, да?

Вот смотри, как это в Java для лямбды выглядит:

Исходник, всё просто:

Function<String, Integer> func = s -> s.length();

А в байт-коде (примерно, после декомпиляции) творится вот такая дичь:

invokedynamic #0:apply:()Ljava/util/function/Function;

И зачем это всё Java, спрашивается?

  • Лямбды, ёба! Раньше компилятор на каждую лямбду городил отдельный анонимный класс — мусора было, как у дурака фантиков. А теперь он генерирует одну статическую реализацию, а invokedynamic её привязывает. Красота!
  • switch по строкам (с Java 7). Тут тоже эта инструкция юзается для эффективного хеширования, чтобы не сравнивать строки в лоб, как дурак.
  • Конкатенация строк (с Java 9). Вместо того чтобы везде совать StringBuilder, теперь стратегия сборки строки выбирается в рантайме. И производительность, блядь, взлетает!

Короче, вывод: invokedynamic — это такой мощный подкаблучник, которого сначала завели для шумных соседей (других JVM-языков), а потом сами с удовольствием им пользуются. И теперь без него ни лямбды, ни много чего другого в Java не было бы так охуенно быстро.