Learn · concepts

Что такое JNI? Java Native Interface для моддеров

JNI (Java Native Interface) — это встроенный мост Java для вызова C/C++ из JVM и обратно. Это стандартный способ загрузки нативной библиотеки модом Minecraft.

TRtrolPublished 5 min read

Что такое JNI простыми словами?

JNI (Java Native Interface) — это часть Java, которая соединяет два мира: управляемый байт-код на JVM и скомпилированный машинный код, написанный на C или C++. Метод получает ключевое слово native и не имеет тела, и во время выполнения JVM связывает его с соответствующей функцией в загруженной нативной библиотеке. Затем обе стороны обмениваются аргументами и возвращаемыми значениями.

Граница не односторонняя. Нативная функция получает указатель JNIEnv, и этот указатель является воротами обратно в Java: он предоставляет вызовы для чтения и записи полей Java, вызова методов Java, создания объектов и возбуждения исключений. Таким образом, тот же мост, который позволяет Java вызывать код, позволяет C вызывать его обратно.

Зачем Minecraft мод или клиент может обращаться к нативному коду?

Большинству Java не нужен JNI. Мод обращается к нему, когда JVM сама по себе не может выполнить задачу, обычно это графика, аудио, интеграция с ОС или рутина, которая должна быть быстрой. Нативная библиотека выполняет основную работу, а JNI — это стандартный способ её загрузки и вызова.

Повторно используйте проверенную библиотеку C

Существующий, хорошо отлаженный код уже решает эту проблему. Переписывать его на Java — пустая трата усилий, поэтому мод подключается к нему.

Достичь операционной системы

Некоторые функции платформы не имеют чистого Java API. Нативный вызов становится единственным способом доступа к ним.

Быстрый путь

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

Устанавливайте драйверы и оборудование

Рендеринг, звук и доступ к устройствам часто скрываются за нативными библиотеками, которые JNI предоставляет Java-коду.

Как работает вызов JNI от начала до конца?

Процесс одинаков на всех платформах: объявляется метод в Java, компилируется соответствующая функция на C в виде общей библиотеки, она загружается во время выполнения, JVM связывает их по имени, а затем происходит вызов. Имена функций следуют фиксированному шаблону, чтобы JVM могла их разрешить, а сгенерированный заголовочный файл определяет точный синтаксис для реализации.

ШагЧто происходит
ОбъявлениеМетод Java объявляется с ключевым словом native и оставляется пустым на стороне Java.
СборкаФункция на C или C++ компилируется в общую библиотеку: .dll в Windows, .so в Linux, .dylib в macOS.
ЗагрузкаJava вызывает System.loadLibrary для загрузки этой библиотеки во время работы программы.
СвязываниеJVM сопоставляет каждый native метод с его функцией на C по искажённому имени.
ВызовJava вызывает метод; управление передается в нативный код и возвращается с результатом.

Во что обходится JNI?

JNI мощный, но у него есть острые углы. Поскольку нативный код выполняется за пределами защитной сети JVM, ошибка там приводит к краху всего процесса, а не к перехватываемому исключению. Память, выделенная нативной стороной, не видна сборщику мусора, поэтому утечки накапливаются, если они не освобождены вручную. Каждое пересечение влечет за собой накладные расходы, что делает вызовы через границу дорогими внутри плотного цикла. И нативные сборки привязаны к платформе: .dll для Windows не загрузится в Linux без отдельной компиляции.

Что оно покупает

  • Прямой доступ к существующим библиотекам C и C++
  • Путь к возможностям ОС и оборудования, недоступным для JVM
  • Производительность, сопоставимая с нативной, для наиболее востребованных участков кода

Что это стоит

  • Коренной сбой может обрушить всю JVM.
  • Ручное управление памятью без страховки сборщика мусора.
  • Сборки для каждой платформы для поддержки и распространения.
  • Накладные расходы, пересекающие границы, в горячих циклах

JNI против Foreign Function и Memory API

JNI — это оригинальный механизм, но теперь он не единственный. Новые версии Java включают Foreign Function and Memory (FFM) API, предназначенный для вызова нативного кода с меньшим количеством шаблонного кода и более строгими гарантиями безопасности. JNI по-прежнему доминирует в существующих инструментариях, поскольку он повсеместен и хорошо изучен.

JNIFFM API
ВозрастОригинальный, присутствует с ранних версий JavaСовременный, добавлен в последних версиях
Шаблонный кодСгенерированные заголовки, C-функции с переименованиемОбработчики методов, определенные в Java, без промежуточного C-кода
Безопасность памятиРучная; ошибки могут повредить JVMОграниченные сегменты с более строгими проверками
РаспространенностьПовсеместно распространен в существующих библиотекахРастет, в новых кодовых базах

Часто задаваемые вопросы

Получить Terminus

Built by people who actually read the JVM internals.