Platform & Advanced APIs
Native Interop: JNI → Panama
- JNI:
nativemethods + generated headers + C glue compiled per platform — powerful, painful, unsafe - JNI crashes take the whole JVM — a segfault in glue code is not an exception
- FFM (JEP 454):
Linker+SymbolLookupbind C functions toMethodHandles — no glue code MemorySegment+Arenamanage off-heap memory with deterministic, scope-checked lifetime- jextract generates Java bindings from C headers automatically
- When possible, avoid interop entirely — a pure-Java library outlives every binding
Linker linker = Linker.nativeLinker();
SymbolLookup libc = linker.defaultLookup();
MethodHandle strlen = linker.downcallHandle(
libc.find("strlen").orElseThrow(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateFrom("Hello, Panama!");
long len = (long) strlen.invokeExact(cString); // 14
} // arena closes → memory freed, further access throwsThe safety upgrade over JNI and Unsafe: a MemorySegment carries bounds and a lifetime (its Arena) — out-of-bounds access and use-after-free throw Java exceptions instead of corrupting the process. Layouts (MemoryLayout.structLayout(...)) describe C structs so field access is named and offset-checked. Upcalls (C calling back into Java) get the same treatment via upcallStub.
JNI (Core Java II ch. 13) remains in the ecosystem — JavaCPP, JNA, and countless legacy bindings — and its rules still matter where you meet it: every JNI reference is loader-local, exceptions must be checked manually after every call, and pinned arrays block the GC. New work should not start there; FFM is faster to write, safer to run, and portable by construction (the binding is data, not compiled C).