Foreign Function & Memory API in Java
Java 22 calling native code and off-heap memory without JNI.
Beyond JNI
Java 22 finalized the Foreign Function & Memory (FFM) API in java.lang.foreign. It lets Java code use off-heap memory and call native C functions without writing any JNI glue code.
Arena and MemorySegment
An **Arena controls when off-heap memory is freed: close it (try-with-resources) and everything it allocated is released. A MemorySegment is a bounds-checked view** of that memory.
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg =
arena.allocate(ValueLayout.JAVA_INT, 3);
seg.setAtIndex(ValueLayout.JAVA_INT, 0, 7);
} // off-heap memory freed hereMeasuring a segment
What does this print?
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg =
arena.allocate(ValueLayout.JAVA_LONG, 2);
seg.setAtIndex(ValueLayout.JAVA_LONG, 1, 5L);
System.out.println(seg.byteSize());
System.out.println(
seg.getAtIndex(ValueLayout.JAVA_LONG, 0));
}16 02 016 5
Show the answer
Two 8-byte longs make 16 bytes (three 4-byte ints would make 12). Index 1 got the 5; index 0 was never written, and arena allocations start zeroed, so it reads 0.
No more wild pointers
Every access is checked. Index 3 in a 3-int segment throws **IndexOutOfBoundsException instead of corrupting memory. Using a segment after its arena is closed throws IllegalStateException** instead of reading freed memory.
// 3 ints: valid indexes are 0..2
seg.setAtIndex(JAVA_INT, 3, 9);
// -> IndexOutOfBoundsException
// later, after the arena was closed:
seg.get(JAVA_BYTE, 0);
// -> IllegalStateExceptionCalling C with a Linker
The **Linker finds a native function through a symbol lookup** and turns it into a MethodHandle, given a **FunctionDescriptor (return and parameter layouts). Writing descriptors by hand gets tedious; jextract** generates the bindings from C headers.
Linker linker = Linker.nativeLinker();
var addr = linker.defaultLookup()
.find("strlen").orElseThrow();
MethodHandle strlen = linker.downcallHandle(
addr, FunctionDescriptor.of(
ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS));Why the warning?
Your FFM code calls a C function, and the JVM prints a warning about restricted methods. Why?
Think about it, then reveal the answer
Linking native functions uses restricted methods: a wrong descriptor can crash the JVM. The JVM warns unless you grant native access explicitly, for example with **--enable-native-access**.
Where FFM is used
Native libraries for compression, cryptography, databases or machine learning can now be called from pure Java, with jextract generating the bindings. Big off-heap buffers also stay out of the GC's way, which helps for large caches and I/O.
Key takeaways
- Arena: lifetime of off-heap memory (try-with-resources)
- MemorySegment checks bounds and lifetime on every access
- Linker + SymbolLookup call C functions without JNI
- jextract generates Java bindings from C headers
MemorySegment offsets are long values, so one segment can exceed 2 GB. A ByteBuffer is indexed by int and stops at about 2 GB.
Practice questions
What does this print?
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg =
arena.allocate(ValueLayout.JAVA_INT, 3);
seg.setAtIndex(ValueLayout.JAVA_INT, 1, 42);
System.out.println(
seg.getAtIndex(ValueLayout.JAVA_INT, 1));
System.out.println(seg.byteSize());
}- 42 24
- 42 3
- 42 12
- 0 12
Check your answer
42 12. allocate(JAVA_INT, 3) reserves room for three 4-byte ints, so byteSize() is 12. Index 1 holds the 42 that was just written.
What does this print?
try (Arena arena = Arena.ofConfined()) {
MemorySegment seg =
arena.allocate(ValueLayout.JAVA_INT, 3);
seg.setAtIndex(ValueLayout.JAVA_INT, 3, 9);
System.out.println("written");
}- Throws IllegalStateException
- Throws IndexOutOfBoundsException
- Throws NullPointerException
- written
Check your answer
Throws IndexOutOfBoundsException. The segment has room for indexes 0 to 2. Writing index 3 would go past the end, so the access is rejected.