FFM API — нативный код и off-heap память без JNI
Тема дорожной карты · Java
Foreign Function & Memory API (FFM) — итог многолетнего Project Panama и штатная замена JNI, финализирован в Java 22 (JEP 454). API решает две задачи: вызов функций нативных библиотек (C ABI) напрямую из Java и безопасная работа с памятью вне кучи. В отличие от JNI не нужно писать «клеевой» код на C и собирать отдельную нативную библиотеку-прослойку — всё описывается на Java, а инструмент jextract генерирует биндинги из готовых заголовочных файлов автоматически.
Как это работает
Три опоры API. Linker связывает Java с ABI платформы и создаёт MethodHandle для downcall (вызов Java → нативная функция) и upcall (обратный вызов из нативного кода в Java-метод). MemorySegment — ограниченное и проверяемое представление области памяти: каждый доступ контролируется по границам и времени жизни. Arena управляет временем жизни выделенной нативной памяти: закрытие арены детерминированно освобождает все её сегменты, без ожидания GC и финализаторов.
static long nativeStrlen(String s) throws Throwable {
Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
linker.defaultLookup().find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
try (Arena arena = Arena.ofConfined()) {
MemorySegment cStr = arena.allocateFrom(s); // копия строки в нативной памяти
return (long) strlen.invokeExact(cStr);
}
}
Практический бонус — производительность: создаваемые линкером связки JIT умеет инлайнить и оптимизировать, поэтому downcall на многих замерах обгоняет эквивалентный вызов через JNI, при этом весь связующий код остаётся на стороне Java и проверяется компилятором. Доступ к сегменту после закрытия арены бросает исключение, а не роняет процесс — принципиальное отличие от JNI и sun.misc.Unsafe, где такая ошибка означает segfault или тихую порчу памяти. Вызовы нативных функций относятся к restricted-методам: в свежих версиях JDK модулю нужно явно выдать право флагом --enable-native-access, иначе JVM печатает предупреждение при первом вызове.
Когда применять
- Интеграция с готовыми нативными библиотеками: криптография, кодеки, ML-рантаймы, системные API операционной системы.
- Off-heap структуры данных и большие буферы, которые не должны нагружать сборщик мусора.
- Модернизация легаси на JNI, JNA или JNR: кода меньше, ошибки ловятся раньше, накладные расходы на вызов ниже.
- При большом C-заголовке —
jextract, чтобы не описывать сотни функций и структур вручную.
Типичные ошибки
- Обращение к
MemorySegmentпосле закрытия его арены: получите исключение — лучше, чем падение процесса, но это всё равно баг владения памятью. - Confined-арена принадлежит одному потоку: доступ из другого даёт
WrongThreadException; для многопоточного использования естьArena.ofShared(). - Неверные layout-ы структур — выравнивание, порядок и размеры полей: данные будут читаться «со сдвигом» без каких-либо исключений.
- Считать, что нативную память освободит GC: временем жизни управляете вы через арены; забытая глобальная арена — это утечка.
- Частые мелкие downcall-ы в цикле: переход Java → натив дешевле, чем в JNI, но не бесплатен — батчируйте вызовы и переносите горячие циклы целиком.
Связанные понятия
Полезные ресурсы
Проверить знания (2)
Загрузка вопросов…