Modern Java

109. Foreign Function & Memory API (FFM)

The Foreign Function & Memory (FFM) API replaces brittle JNI with safe off-heap memory management (Arena, MemorySegment) and direct native C library invocations.

Java 22+ (JEP 454 Final)

Explanation

For decades, interfacing Java with native libraries (C/C++, GPU drivers, OpenSSL) required the Java Native Interface (JNI). JNI was notoriously complex: writing separate C bridge headers, compiling platform-specific .so/.dylib files, slow marshaling, and crash risks where bad pointers killed the JVM process.

JEP 454 (Java 22) finalized the Foreign Function & Memory (FFM) API (java.lang.foreign), offering two revolutionary capabilities: 1. Foreign Memory Access: MemorySegment and Arena allow allocating, manipulating, and releasing native off-heap memory safely. Arenas provide deterministic lifecycles (Arena.ofConfined() ensures instant memory deallocation when leaving a try-with-resources block), preventing off-heap memory leaks. 2. Foreign Function Invocation: Linker and SymbolLookup look up native C symbols dynamically (e.g. strlen, puts, BLAS math libraries) and invoke them directly from Java using strongly typed MethodHandle instances, with zero C boilerplate!

Example

import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;

public class FfmNativeCall {
    public static void main(String[] args) throws Throwable {
        // 1. Deterministic off-heap memory allocation via Arena
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment nativeString = arena.allocateFrom("Hello from FFM Native Memory!");
            System.out.println("Native byte size: " + nativeString.byteSize());
            System.out.println("String content:   " + nativeString.getString(0));
            // Memory is automatically and deterministically freed when leaving try block!
        }

        // 2. Call standard C library function 'strlen' directly
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdlib = linker.defaultLookup();
        MemorySegment strlenAddress = stdlib.find("strlen").orElseThrow();

        // C signature: size_t strlen(const char *s)
        FunctionDescriptor descriptor = FunctionDescriptor.of(
            ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
        MethodHandle strlen = linker.downcallHandle(strlenAddress, descriptor);

        try (Arena arena = Arena.ofConfined()) {
            MemorySegment cStr = arena.allocateFrom("Modern Java 22 FFM");
            long length = (long) strlen.invokeExact(cStr);
            System.out.println("C strlen returned: " + length); // 18
        }
    }
}

Sources