Foreign Function and Memory API comes under project Panama launched with the aim to ease the interaction between Java and foreign (non-Java) APIs, i.e., native code which can be written in C, C++ or even Assembly Language. We need this because there are many native only libraries which are not written in java. Some of these include:
The way to achieve this, earlier was using JNI (Java Native Interface)
JNI allowed classes to have native methods. These methods would not have a body and their implementation would be written in a native language (C, C++ etc).
There were multiple downsides of this approach:
Overall, all these downsides led to Project panama. Project panama has multiple tools and apis to achieve its goals:
In this blog, our goal is to take a look at the Foreign Function and Memory API.
In brief, FFM is an API that enables the interoperation of Java programs with code and data outside of the Java runtime by efficiently invoking foreign functions (i.e., code outside the JVM) and safely accessing foreign memory (i.e., memory not managed by the JVM), avoiding the fragility and risks associated with JNI.
When we create an object using a new keyword in java, it gets stored on-heap in the JVM. Garbage collector has access to this memory. Garbage collection can be costly and unpredictable and most of the performance critical libraries need the data to be stored off-heap. The native languages handle the allocation and deallocation themselves. Java has provided access to off-heap storage using ByteBufferAPI and sun.misc.Unsafe API in the past.
Both of them have their own issues, for example, in case of ByteBuffer API, the maximum size of region is limited to two gigabytes and the deallocation is controlled by the garbage collector when the buffer object is collected and not by the developer. In the case of the Unsafe API, it gives too much fine grained control to the developer making it a weak programming model, where an application can interact with multiple regions of the off-heap memory. There are also chances of having dangling pointers which can cause their own bugs. Hence we needed something more balanced to access off-heap memory. Here comes Foreign Function and Memory API:
We will look at all of these briefly and then will walk through a code example that calls a method written in C using FFM in java and outputs the result.
A memory segment is an abstract representation of a contiguous block of memory, which can reside either off-heap or on-heap. They ensure that the memory access operations are safe by providing both spatial (range of memory addresses associated with a segment) and temporal (lifetime of the memory region before it is deallocated) bounds. A memory segment can be:
Arena:
An arena defines the bounds of the memory segment it allocates. Example:
There are multiple types of arenas. They are described in the table below:
FFM API includes SegmentAllocator as an abstraction for allocation and initialization of memory segments. The Arena class implements this interface allowing them the ability to allocate native memory segments.
MemoryLayout is used to describe the content of a memory segment in a more declarative fashion. There are various types of memory layouts available to us, including:
The very first step when it comes to supporting the calling of foreign functions is having the ability to find the address of the given symbol (function or a global variable) in the native library that needs to be called. In the FFM api this is defined as a functional interface: SymbolLookup. The SymbolLookup is created with respect to a particular library and then the find(String) method takes the name of a symbol to return its address in that library.
For example,
The above code defines a SymbolLookup for a library “Test”. It then uses the “find” function to retrieve the symbol test (a method that might have been defined in this library). It returns the address of the symbol in case the symbol is found otherwise it throws a RuntimeException.
The FFM API defines 3 kinds of SymbolLookup objects:
Once we have the memory address of our required native function using SymbolLookup, we need an interface that enables our java code to interoperate with the native code. The Linker interface serves this purpose. It enables both downcalls (calling native code from java code) and upcalls (calling java code from native code). Linker::nativeLinker() returns the linker for the ABI associated with the underlying native platform (combination of OS and processor where the Java runtime is currently executing). The nativeLinker is optimized for the calling conventions of multiple different platforms.
For downcalls, the MethodHandle::downcallHandle comes handy. It takes the address of the foreign function obtained from SymbolLookup. We can then use the invoke method of the MethodHandle to invoke the downcall from our java code. We can even pass arguments in the MethodHandle, which will then be passed to the native function.
For upcalls, FFM provides us with a MemorySegment::upcallStub. It takes a methodHandle (usually the java method), converts it into a MemorySegment Instance and passes it as a function pointer to the native code.
To create the upcall stubs and downcall MethodHandles we also need a description of the signature of the foreign function. This is modelled using a FunctionDescription interface. It describes the parameter types and return type of the target foreign function. In case a C function has a return type void, then its FunctionDescriptor can be obtained using FunctionDescriptor.ofVoid(ADDRESS).
In this simple demonstration we will write a simple method in C that will return the result by adding two given numbers as arguments.
Lets, define the simple C method first in a file named: addition.c:
Now that we have our C method saved, we will compile the C code to a shared library (addition.so on Unix-like systems, addition.dll on Windows and addition.dylib on macOS)
Once the C code is compiled, we will move on to writing our java program that will access this foreign method, pass the arguments and print the sum.
Steps
Code:
Output:
Finally, when we can run this program with –enable-preview (since it is a preview feature in java 22), we can see the output as the sum of 2 integers:
The aim of FFM is to replace the brittle machinery of native methods and the Java Native Interface (JNI) with a concise, readable, and pure-Java API. It provides foreign function and memory access with performance comparable to JNI and sun.misc.Unsafe API with a much uniform approach and a guarantee of no use-after-free bugs. Many situations that previously required using JNI can now be handled by invoking methods in the Foreign Function & Memory API, maintaining the integrity of the Java Platform. The warnings in the output ensure that the user is aware of performing unsafe operations with native code.
To learn more about the FFM API, refer to the official documentation.