Learn · concepts
Что такое JNI? Java Native Interface для моддеров
JNI (Java Native Interface) — это встроенный мост Java для вызова C/C++ из JVM и обратно. Это стандартный способ загрузки нативной библиотеки модом Minecraft.
Что такое 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 по-прежнему доминирует в существующих инструментариях, поскольку он повсеместен и хорошо изучен.
| JNI | FFM API | |
|---|---|---|
| Возраст | Оригинальный, присутствует с ранних версий Java | Современный, добавлен в последних версиях |
| Шаблонный код | Сгенерированные заголовки, C-функции с переименованием | Обработчики методов, определенные в Java, без промежуточного C-кода |
| Безопасность памяти | Ручная; ошибки могут повредить JVM | Ограниченные сегменты с более строгими проверками |
| Распространенность | Повсеместно распространен в существующих библиотеках | Растет, в новых кодовых базах |
Часто задаваемые вопросы
Оно встроено. JNI поставляется вместе с JDK и JVM, поэтому Java-части не требуется ничего дополнительного. Компиляция нативной части всё ещё требует C или C++ инструментальной цепочки для каждой целевой платформы.
Нет. Java-часть остаётся переносимой, но нативная библиотека компилируется для одной операционной системы и процессора. Кроссплатформенная поставка означает создание и упаковку отдельных .dll, .so и .dylib файлов.
Нет. JNI — это оригинальный механизм. Недавние выпуски Java добавляют API Foreign Function и Memory, который призван быть более безопасным и требовать меньше шаблонного кода, но JNI по-прежнему широко используется и хорошо понятен.
Когда JVM не может сделать что-то напрямую (пообщаться с GPU или аудио-подсистемой, вызвать платформенно-зависимый API или выполнить критически важную для производительности рутину), мод загружает нативную библиотеку и вызывает её через JNI.
Built by people who actually read the JVM internals.