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
Calling a C function
The example below calls
OUTPUT of the above command
Explanation of the code
For an application packaged as an executable JAR, the manifest attribute
OUTPUT of the above code
Unlike
Describing a C struct with a memory layout
Native libraries pass data in
OUTPUT of the above code
Such a segment can be passed to a native function which expects a
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.MemoryLayoutandValueLayout- describe the shape of memory: a Cint, along, a pointer, or a wholestruct.Linker,SymbolLookupandFunctionDescriptor- find a native function, describe its parameters and return type, and create aMethodHandlewhich calls it - a downcall. The reverse, letting C code call a Java method, is an upcall.
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
strlen returned 14
linker.defaultLookup()finds symbols in the C standard library;SymbolLookup.libraryLookup(path, arena)loads any other native library.- The
FunctionDescriptordescribes the C signature:size_tmaps toJAVA_LONGon 64-bit systems, andconst char*toADDRESS. The resultingMethodHandleis called withinvokeExact(), 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.
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-accessWARNING: 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
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");
}
}
}
}
Segment size: 4000000 bytes, sum = 49500000 Out-of-bounds access caught: IndexOutOfBoundsException
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.
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) + ")");
}
}
}
}
sizeof(struct Point) = 8 points[0] = (0, 1) points[1] = (10, 11) points[2] = (20, 21)
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 _.Continue Reading Java 22 to 25 Tutorials
Overview of New Features in Java 22Click to read tutorial: Overview of New Features in Java 22 Java 22 Unnamed Variables and Patterns with _Click to read tutorial: Java 22 Unnamed Variables and Patterns with _ Java 22: Run Multi-File Programs Without CompilingClick to read tutorial: Java 22: Run Multi-File Programs Without Compiling Overview of New Features in Java 23Click to read tutorial: Overview of New Features in Java 23 Overview of New Features in Java 24Click to read tutorial: Overview of New Features in Java 24 Overview of New Features in Java 25Click to read tutorial: Overview of New Features in Java 25 Java 23 Markdown Documentation CommentsClick to read tutorial: Java 23 Markdown Documentation Comments Java 24 Stream Gatherers Tutorial with ExamplesClick to read tutorial: Java 24 Stream Gatherers Tutorial with Examples Java 24 Class-File API Tutorial with ExamplesClick to read tutorial: Java 24 Class-File API Tutorial with Examples
Overview of New Features in Java 22Click to read tutorial: Overview of New Features in Java 22 Java 22 Unnamed Variables and Patterns with _Click to read tutorial: Java 22 Unnamed Variables and Patterns with _ Java 22: Run Multi-File Programs Without CompilingClick to read tutorial: Java 22: Run Multi-File Programs Without Compiling Overview of New Features in Java 23Click to read tutorial: Overview of New Features in Java 23 Overview of New Features in Java 24Click to read tutorial: Overview of New Features in Java 24 Overview of New Features in Java 25Click to read tutorial: Overview of New Features in Java 25 Java 23 Markdown Documentation CommentsClick to read tutorial: Java 23 Markdown Documentation Comments Java 24 Stream Gatherers Tutorial with ExamplesClick to read tutorial: Java 24 Stream Gatherers Tutorial with Examples Java 24 Class-File API Tutorial with ExamplesClick to read tutorial: Java 24 Class-File API Tutorial with Examples