With the JDK 22 and the JEP 424, I discovered a new feature : Foreign Function and Memory API (FFM).
Before diving into, let me introduce the motivations and goals behind this. Java Platform offers a rich foundation of tools, libraries and drivers (JDBC, HTTP, NIO, sockets), but sometimes developers have to deal with resources outside the JVM. FFM was created to interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions, and by safely accessing foreign memory, the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI.
Basically, the FFM API resides in the java.lang.foreign
package of the java.base
module.
Developer, allocate access and manage foreign memory and communicate outside the JVM.
MemorySegment
represents a contiguous region of memory, while MemoryAddress
provides a pointer to a specific memory location within a segment.
SegmentAllocator
interface allows to allocate memory segments with specific characteristics, such as size and alignment.
To call a foreign function developer have Linker, FunctionDescriptor, and SymbolLookup
How to use it
Instantiate the Linker
The Linker plays a vital role in enabling Java programs to communicate outside the JVM. Most of the time the developer will use the nativeLinker(), an optimized version for OS and Processor architecture. This ensures compatibility and efficiency when interacting with native libraries and functions. However, if your project has specific requirements or you’re exploring alternative options, you may consider third-party native linkers. Be sure to evaluate them based on factors such as platform compatibility, performance, ease of use, community support, and reliability.
Find symbols from native libraries
Symbols are functions or variables defined in external libraries that you want to access from your Java code.
To do this there are 3 steps :
- get the address of a symbol in one or more libraries.
- specify the signature and calling convention of the function you want to invoke.
FunctionDescriptor
interface defines the parameters and return type of the function, as well as other characteristics such as calling conventions - Once you have obtained the address of the symbol and defined its descriptor, you can transform it into a
MethodHandle
using theMethodHandles API
Example for a function that takes two integers as parameters and returns a double and his transformation to MethodHandle
allows to invoke the function in java code
// Define the parameter types and return type
MemoryLayout intLayout = MemoryLayouts.JAVA_INT; // Represents an int
MemoryLayout doubleLayout = MemoryLayouts.DOUBLE; // Represents a double
// Define the FunctionDescriptor
FunctionDescriptor descriptor = FunctionDescriptor.of(CLinker.C_DOUBLE, intLayout, intLayout);
MethodHandle handle = lookup.lookupFunction("myFunction", descriptor);
Allocate Memory Area
To interact with external libraries that require manual memory management FFM allows developers to allocate and manage memory outside the Java Heap. Without this, parameters may not be readable for the underlying function, as the necessary memory space does not exist within the Java runtime.
In reality, developers need to store pointers to data, rather than the data itself. This can be cumbersome, especially when dealing with complex data structures like lists, as each item in the list must be individually allocated.
Invoke the function
Invoke the function and pass the Memory Area containing the parameters.
Thanks to the MethodHandle
you can now invoke it.
Suppose you have a native function myFunction
defined in an external library that takes two integers as parameters and returns a double.
double result = (double) handle.invoke(arg1, arg2);
Note :There is no guarantee that the asType call is actually made. If the JVM can predict the results of making the call, it may perform adaptations directly on the caller’s arguments, and call the target method handle according to its own exact type. see : https://wiki.openjdk.org/display/HotSpot/Deconstructing+MethodHandles
(If needed !) read the result in of heap memory Area
If, as with the radixsort
function, there are no returns and the inserted data is modified directly, you should re-read the data in the (same :)) Area memory.
(as is in the example below)
Entire example
You will find here the example shared in the JEP, hope this will let you more the concepts :
// 1. Find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixSort = linker.downcallHandle(
stdlib.lookup("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. Allocate off-heap memory to store four pointers
SegmentAllocator allocator = SegmentAllocator.implicitAllocator();
MemorySegment offHeap = allocator.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
// 4. Copy the strings from on-heap to off-heap
for(int i = 0; i < javaStrings.length; i++) {
// Allocate a string off-heap, then store a pointer to it
MemorySegment cString = allocator.allocateUtf8String(javaStrings[i]);
offHeap.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
// 5. Sort the off-heap data by calling the foreign function
radixSort.invoke(offHeap, javaStrings.length, MemoryAddress.NULL, '\0');
// 6. Copy the (reordered) strings from off-heap to on-heap
for (int i = 0; i < javaStrings.length; i++) {
MemoryAddress cStringPtr = offHeap.getAtIndex(ValueLayout.ADDRESS, i);
javaStrings[i] = cStringPtr.getUtf8String(0);
}
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"}); // true
You can go further in Memory management (sessions, segment, (un)safety) and Looking up foreign functions with openGL lookup example here : https://openjdk.org/jeps/424
As we can see here, the process is simpler than with JNI integration with less boilerplate code, Simplified API, lightweight memory management and type safety.
I personally hope this will be generalized more and used by most java developers, who today tend to over-eng their projects with dependencies that aren’t always necessary, reducing the overall safety of their services.
I’d like to take this opportunity to share some feedback from the RocksDb teams who tested FFM : https://rocksdb.org/blog/2024/02/20/foreign-function-interface.html
Overall, Panama/FFI offers benefits such as improved performance, reduced boilerplate code, and enhanced interoperability, making it a promising technology for enhancing the RocksDB Java API and enabling new opportunities in the future.
Hope you enjoyed this short introduction about FFM with some prémise of Panama’s Foreign-Memory Access API concepts. If I made a mistake let me know and contact me, nobody is perfect. Soon I’d like to share some discoveries about JRE and Memory Areas, see ya :)