Java 22 Foreign Function & Memory API Tutorial

Introduction Java programs sometimes need to step outside the JVM: to call a native library written in C - a graphics or machine learning library, an operating system API, a database driver - or to work with large amounts of memory outside the Java heap, which the garbage collector does not manage. For decades, the only standard tool for calling native code was the Java Native Interface (JNI), which requires writing C glue code, generating header filesClick to read: generating header files and compiling a native library for each platform. Off-heap memory was handled with ByteBuffer, limited to 2 GB, or the unsupported sun.misc.Unsafe. The Foreign Function & Memory (FFM) API, developed by Project Panama over six incubator and preview releases and final in Java 22Click to read tutorial: Overview of New Features in Java 22 (JEP 454), replaces both with a safe, pure-Java API in the java.lang.foreign package. This tutorial explains the FFM API with examples - calling a C function, managing off-heap memory with arenas and memory segments, and describing C structs with memory layouts. The main types
  • MemorySegment - a bounded region of memory, on or off the Java heap. Every access is checked against its bounds, and against its lifetime: a segment can no longer be used once its memory has been freed.
  • Arena - controls the lifetime of the memory segments allocated in it. When a confined or shared arena is closed, all its memory is freed at once, deterministically.
  • MemoryLayout and ValueLayout - describe the shape of memory: a C int, a long, a pointer, or a whole struct.
  • Linker, SymbolLookup and FunctionDescriptor - find a native function, describe its parameters and return type, and create a MethodHandle which calls it - a downcall. The reverse, letting C code call a Java method, is an upcall.
Calling a C function The example below calls strlen from the C standard library, which every platform provides, to measure the length of a C string -
Calling the C function strlen from Java
//StrlenDemo.java
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;

public class StrlenDemo {
  public static void main(String[] args) throws Throwable {
    Linker linker = Linker.nativeLinker();
    MemorySegment strlenAddress = linker.defaultLookup().find("strlen").orElseThrow();
    // size_t strlen(const char *s): returns a long, takes a pointer
    MethodHandle strlen = linker.downcallHandle(strlenAddress,
        FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));

    try (Arena arena = Arena.ofConfined()) {
      MemorySegment cString = arena.allocateFrom("Hello, Panama!");   // copied off-heap, NUL-terminated
      long length = (long) strlen.invokeExact(cString);
      System.out.println("strlen returned " + length);
    }                                                                  // memory freed here
  }
}
java --enable-native-access=ALL-UNNAMED StrlenDemo
 OUTPUT of the above command
strlen returned 14
Explanation of the code
  • linker.defaultLookup() finds symbols in the C standard library; SymbolLookup.libraryLookup(path, arena) loads any other native library.
  • The FunctionDescriptor describes the C signature: size_t maps to JAVA_LONG on 64-bit systems, and const char* to ADDRESS. The resulting MethodHandle is called with invokeExact(), whose argument and return types must match exactly - hence the (long) cast.
  • arena.allocateFrom("Hello, Panama!") copies the Java string into off-heap memory as a NUL-terminated UTF-8 C string. When the try-with-resources block ends, the arena is closed and the memory is freed.
  • No C code was written or compiled - the whole binding is in Java.
Native access must be enabled. Calling native code can crash the JVM, so methods like downcallHandle() are restricted. Without --enable-native-access, the JVM prints a warning on first use, and a future release will block them -  Warnings without --enable-native-access
WARNING: A restricted method in java.lang.foreign.Linker has been called
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
For an application packaged as an executable JAR, the manifest attribute Enable-Native-Access: ALL-UNNAMED does the same. Working with off-heap memory Memory segments can also be used purely from Java, as safe off-heap storage - for large data sets which should not burden the garbage collector, or for memory shared with native code -
Allocating and using off-heap memory
//OffHeapDemo.java
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;

public class OffHeapDemo {
  public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
      int count = 1_000_000;
      MemorySegment ints = arena.allocate(ValueLayout.JAVA_INT, count);   // 4 MB off-heap
      for (int i = 0; i < count; i++) {
        ints.setAtIndex(ValueLayout.JAVA_INT, i, i % 100);
      }
      long sum = 0;
      for (int i = 0; i < count; i++) sum += ints.getAtIndex(ValueLayout.JAVA_INT, i);
      System.out.println("Segment size: " + ints.byteSize() + " bytes, sum = " + sum);

      try {
        ints.getAtIndex(ValueLayout.JAVA_INT, count);                     // one past the end
      } catch (IndexOutOfBoundsException e) {
        System.out.println("Out-of-bounds access caught: IndexOutOfBoundsException");
      }
    }
  }
}
 OUTPUT of the above code
Segment size: 4000000 bytes, sum = 49500000
Out-of-bounds access caught: IndexOutOfBoundsException
Unlike Unsafe, an out-of-bounds read throws an exception instead of reading arbitrary memory, and using a segment after its arena is closed throws IllegalStateException - so the classic C bugs of buffer overflows and use-after-free cannot happen through this API.
Describing a C struct with a memory layout Native libraries pass data in structs. A StructLayout describes one, and VarHandles derived from it read and write its fields by name -
An array of C structs - struct Point with int fields x and y
//StructDemo.java
import java.lang.foreign.Arena;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.StructLayout;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.VarHandle;

public class StructDemo {
  static final StructLayout POINT = MemoryLayout.structLayout(
      ValueLayout.JAVA_INT.withName("x"),
      ValueLayout.JAVA_INT.withName("y"));
  static final VarHandle X = POINT.varHandle(MemoryLayout.PathElement.groupElement("x"));
  static final VarHandle Y = POINT.varHandle(MemoryLayout.PathElement.groupElement("y"));

  public static void main(String[] args) {
    try (Arena arena = Arena.ofConfined()) {
      MemorySegment points = arena.allocate(POINT, 3);          // struct Point points[3]
      for (int i = 0; i < 3; i++) {
        MemorySegment p = points.asSlice(i * POINT.byteSize(), POINT);
        X.set(p, 0L, i * 10);
        Y.set(p, 0L, i * 10 + 1);
      }
      System.out.println("sizeof(struct Point) = " + POINT.byteSize());
      for (int i = 0; i < 3; i++) {
        MemorySegment p = points.asSlice(i * POINT.byteSize(), POINT);
        System.out.println("points[" + i + "] = (" + X.get(p, 0L) + ", " + Y.get(p, 0L) + ")");
      }
    }
  }
}
 OUTPUT of the above code
sizeof(struct Point) = 8
points[0] = (0, 1)
points[1] = (10, 11)
points[2] = (20, 21)
Such a segment can be passed to a native function which expects a struct Point*. For a large C library, writing these layouts and descriptors by hand is tedious; the jextract tool, available from the OpenJDK project, generates them automatically from the library's header files. Summary In this tutorial we saw how the Foreign Function & Memory API, final in Java 22, lets Java call native code and manage off-heap memory without JNI or Unsafe. We called the C function strlen through a downcall handle, saw why native access must be enabled with --enable-native-access, allocated and safely accessed a million off-heap integers in an Arena, and described an array of C structs with a StructLayout. Next, read about unnamed variables and patternsClick to read tutorial: Java 22 Unnamed Variables and Patterns with _.