13 Foreign Function and Memory API
The Foreign Function and Memory (FFM) API enables Java programs to interoperate with code and data outside the Java runtime. This API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI. The API invokes foreign functions, code outside the JVM, and safely accesses foreign memory, memory not managed by the JVM.
Note:
This is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.For background information about the Foreign Function and Memory API, see JEP 424.
The FFM API is contained in the package java.lang.foreign.
Calling a C Library Function with the Foreign Function and Memory API
Consider the strlen C standard library function:
size_t strlen(const char *s);
It takes one argument, a string, and returns the length of the string. To call this function from a Java application, you would follow these steps:
- Allocate off-heap memory, which is memory outside the Java runtime, for the strlen function's argument. See Allocating Off-Heap Memory.
- Store the Java string in the off-heap memory that you allocated. See
Dereferencing Off-Heap Memory.
Note:
Some methods enable you to perform these two steps with one method call; see Methods That Allocate and Populate Off-Heap Memory. - Build and then call a method handle that points to the strlen function. See Linking and Calling a C Function.
The following example calls strlen with the Foreign Function and Memory (FFM) API:
static long invokeStrlen(String s) throws Throwable {
try (MemorySession session = MemorySession.openConfined()) {
// 1. Allocate off-heap memory, and
// 2. Dereference off-heap memory
MemorySegment nativeString = session.allocateUtf8String(s);
// 3. Link and call C function
// 3a. Obtain an instance of the native linker
Linker linker = Linker.nativeLinker();
// 3b. Locate the address of the C function
SymbolLookup stdLib = linker.defaultLookup();
MemorySegment strlen_addr = stdLib.lookup("strlen").get();
// 3c. Create a description of the C function signature
FunctionDescriptor strlen_sig =
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
// 3d. Create a downcall handle for the C function
MethodHandle strlen = linker.downcallHandle(strlen_addr, strlen_sig);
// 3e. Call the C function directly from Java
return (long)strlen.invoke(nativeString);
}
}
Allocating Off-Heap Memory
Off-heap data is data stored in memory outside the Java runtime and therefore not subject to garbage collection. Off-heap data is stored in off-heap memory, which is represented by a MemorySegment object. To invoke a C function from a Java application, its arguments must be in off-heap memory.
The following example create a memory segment off-heap to store the contents of a Java string:
String s = ...
MemorySegment nativeString =
MemorySegment.allocateNative(s.length()+1, MemorySession.global());
This example allocates a block of off-heap memory of size s.length() +
1
, which is large enough to hold the contents of the Java string, plus the
trailing terminator character.
All memory segments are associated with a memory session, which determines when the memory segment is valid or can be accessed. A memory session is represented by a MemorySession object. In the previous example, the memory segment is associated with the global memory session, which is a memory session that cannot be closed. As a result, the off-heap memory will only be deallocated when the JVM process ends. If you want a closeable memory session, then you can use a confined memory session:
String s = ...
try (MemorySession session = MemorySession.openConfined()) {
MemorySegment.allocateNative(s.length()+1, session);
// ...
} // nativeString is deallocated here
A confined memory session is a session that can only be accessed by
the current thread. This example creates a confined memory session in a
try
-with-resources statement. At the end of the
try
-with-resources block, the confined memory session is closed and
all the off-heap memory associated with it is released.
Dereferencing Off-Heap Memory
After creating an empty memory segment, nativeString
, the
next step is to copy the characters of the Java string into the segment:
for(int i = 0; i < s.length(); i++) {
nativeString.set(ValueLayout.JAVA_BYTE, i, (byte)s.charAt(i));
}
// Add the string terminator at the end
nativeString.set(ValueLayout.JAVA_BYTE, s.length(),(byte)0);
The first argument of the MemorySegment::set method is of type ValueLayout.OfByte. A value layout models the memory layout associated with values of basic data types such as primitives. A value layout encodes the size, the endianness, the alignment of the piece of memory to be dereferenced, and the Java type to be used for the dereference operation. In this example, ValueLayout.JAVA_BYTE has a size of one byte and native endianness, although this is irrelevant as the example is only reading a single byte.
When using this layout, the API expects clients to encode dereferenced values in Java as the primitive type byte. The second argument of the MemoryLayout::set method is the index, relative to the address of the memory segment, where to write the byte value.
You can allocate and populate more complex data types in off-heap memory. See Memory Layouts and Structured Access.
Methods That Allocate and Populate Off-Heap Memory
The SegmentAllocator interface contains methods that both allocate
off-heap memory and copy Java data into it. The following invokeStrlen(String
s)
example uses the class MemorySession,
which implements the SegmentAllocator class. The example
calls the method SegmentAllocator::allocateUtf8String, which converts a string
into a UTF-8 encoded, null-terminated C string, then stores the result into a memory
segment.
static long invokeStrlen(String s) throws Throwable {
try (MemorySession session = MemorySession.openConfined()) {
// 1. Allocate off-heap memory, and
// 2. Dereference off-heap memory
MemorySegment nativeString = session.allocateUtf8String(s);
// ...
}
}
Linking and Calling a C Function
Calling a native function, such as the strlen C standard library function, involves the following steps:
Obtaining an Instance of the Native Linker
A linker provides access to foreign functions from Java code, and access to Java code from foreign functions. The native linker provides access to the libraries that adhere to the calling conventions of the platform in which the Java runtime is running. These libraries are referred to as "native" libraries.
Linker linker = Linker.nativeLinker();
Locating the Address of the C Function
To call a native method such as strlen
, you need a downcall
method handle, which is a MethodHandle instance that points to a native function. This
instance requires the address of the native function. The following statements obtain
the address of the strlen
function:
Linker linker = Linker.nativeLinker();
SymbolLookup libc = SymbolLookup.libraryLookup("libc.so.6", session);
MemorySegment strlen_addr = libc.lookup("strlen").get();
SymbolLookup::libraryLookup(String, MemorySession) creates a
library lookup, which locates all the symbols in a user-specified native library. It
loads the native library and associates it with a MemorySession object. In this example,
libc.so.6
is the file name of the C standard library for many
Linux systems.
Because strlen is part of the C standard library, you can use instead the native linker's default lookup. This is a symbol lookup for symbols in a set of commonly used libraries (including the C standard library). This means that you don't have to specify a system-dependent library file name:
// 3a. Obtain an instance of the native linker
Linker linker = Linker.nativeLinker();
// 3b. Locate the address of the C function
SymbolLookup stdLib = linker.defaultLookup();
MemorySegment strlen_addr = stdLib.lookup("strlen").get();
Creating the Description of the C Function Signature
A downcall method handle also requires a description of the native function's signature, which is represented by a FunctionDescriptor instance. A function descriptor describes the layouts of the native function arguments and its return value (if any). The following creates a function descriptor for the strlen function:
// 3c. Create a description of the C function signature
FunctionDescriptor strlen_sig =
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS);
The first argument of the FunctionDescriptor::of method is the layout of the native function's return value. Native primitive types are modeled using value layouts whose size matches that of the native primitive type. This means that a function descriptor is platform-specific. For example, size_t has a layout of JAVA_LONG on 64-bit or x64 platforms but a layout of JAVA_INT on 32-bit or x86 platforms.
The subsequent arguments of FunctionDescriptor::of are the layouts of the native function's
arguments. In this example, it's ValueLayout.ADDRESS; all pointer types are modeled with this
value layout. Note that struct
types, which aren't shown here, are
modeled with struct layouts. See Memory Layouts and Structured Access.
Creating the Downcall Handle for the C Function
The following statement creates a downcall method handle for the strlen function with its address and function descriptor.
// 3d. Create a downcall handle for the C function
MethodHandle strlen = linker.downcallHandle(strlen_addr, strlen_sig);
A downcall method handle has a MethodType
associated with it, which represents the arguments and return type accepted and returned
by a method handle. In this example, the MethodType
associated with strlen
has one parameter, Addressable, and its return type is long
. Call MethodHandle::type to obtain a method handle's MethodType. See Downcall method handles in the
Linker interface JavaDoc API specification for
information about how a method's type is derived.
Calling the C Function Directly from Java
The following statement calls the strlen function with a memory segment that contains the function's argument:
// 3e. Call the C function directly from Java
return (long)strlen.invoke(nativeString);
You need to cast a method handle invocation with the expected return type; in
this case, it's long
.
Upcalls: Passing Java Code as a Function Pointer to a Foreign Function
An upcall is a call from native code back to Java code. More specifically, it enables you to pass Java code as a function pointer to a foreign function.
Consider the standard C library function qsort
, which sorts
the elements of an array:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
It takes four arguments:
base
: Pointer to first element of the array to be sortednbemb
: Number of elements in the arraysize
: Size, in bytes, of each element in the arraycompar
: Pointer to function that compares two elements
The following example calls the qsort
function to sort an
int
array. However, this method requires a pointer to a function
that compares two array elements. The example defines a comparison method
(Qsort::qsortCompare
), creates a method handle to represent this
comparison method, and then creates a function pointer from this method handle.
class Qsort {
static int qsortCompare(MemoryAddress addr1, MemoryAddress addr2) {
return Integer.compare(
addr1.get(ValueLayout.JAVA_INT, 0),
addr2.get(ValueLayout.JAVA_INT, 0));
}
}
static int[] qsortTest (int[] unsortedArray) throws Throwable {
int[] sortedArray;
try (MemorySession session = MemorySession.openConfined()) {
// Allocate off-heap memory and store unsortedArray in it
MemorySegment array = session.allocateArray(
ValueLayout.JAVA_INT,
unsortedArray);
// Obtain instance of native linker
Linker linker = Linker.nativeLinker();
// Create downcall handle for qsort
MethodHandle qsort = linker.downcallHandle(
linker.defaultLookup().lookup("qsort").get(),
FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.JAVA_LONG,
ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS)
);
// Create method handle for qsortCompare
FunctionDescriptor compareFunc_sig = FunctionDescriptor.of(
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS,
ValueLayout.ADDRESS);
MethodHandle comparHandle = MethodHandles.lookup().findStatic(
Qsort.class, "qsortCompare", Linker.upcallType(compareFunc_sig));
// Create function pointer for qsortCompare
MemorySegment comparFunc = linker.upcallStub(comparHandle,
compareFunc_sig, session);
// Call qsort
qsort.invoke(array, (long) unsortedArray.length, 4L, comparFunc);
// Dereference off-heap memory
sortedArray = array.toArray(ValueLayout.JAVA_INT);
}
return sortedArray;
}
The following statement allocates off-heap memory, then stores the
int
array to be sorted in it:
// Allocate off-heap memory and store unsortedArray in it
MemorySegment array = session.allocateArray(
ValueLayout.JAVA_INT,
unsortedArray);
The following statements create a downcall method handle for the qsort
function:
// Obtain instance of native linker
Linker linker = Linker.nativeLinker();
// Create downcall handle for qsort
MethodHandle qsort = linker.downcallHandle(
linker.defaultLookup().lookup("qsort").get(),
FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.JAVA_LONG,
ValueLayout.JAVA_LONG,
ValueLayout.ADDRESS)
);
The following class defines the Java method that compares two elements, in
this case two int
values:
class Qsort {
static int qsortCompare(MemoryAddress addr1, MemoryAddress addr2) {
return Integer.compare(
addr1.get(ValueLayout.JAVA_INT, 0),
addr2.get(ValueLayout.JAVA_INT, 0));
}
}
In this method, the int
values are represented by MemoryAddress objects. A memory address models a
reference to a memory location. To obtain a value from a memory address, call one of its
get methods. This example calls the get(ValueLayout.OfInt, long), where the second argument is the
offset in bytes relative to the memory address's location. The second argument is 0
because the memory addresses in this example store only one value.
The following statement creates a method handle to represent the comparison
method Qsort::qsortCompare
:
// Create method handle for qsortCompare
FunctionDescriptor compareFunc_sig = FunctionDescriptor.of(
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS,
ValueLayout.ADDRESS);
MethodHandle comparHandle = MethodHandles.lookup().findStatic(
Qsort.class, "qsortCompare", Linker.upcallType(compareFunc_sig));
The MethodHandles.Lookup::findStatic method creates a method handle for a static method. It takes three arguments:
- The method's class
- The method's name
- The method's type: The first argument of MethodType::methodType is the method's return value's type. The rest are the types of the method's arguments.
The following statement creates a function pointer from the method handle
comparHandle
:
// Create function pointer for qsortCompare
MemorySegment comparFunc = linker.upcallStub(comparHandle,
compareFunc_sig, session);
The Linker::upcallStub method takes three arguments:
- The method handle from which to create a function pointer
- The function pointer's function descriptor; in this example, the
arguments for FunctionDescriptor.of
correspond to the return value type and arguments of
Qsort::qsortCompare
- The memory session to associate with the function pointer (which is a memory segment)
The following statement calls the qsort
function:
// Call qsort
qsort.invoke(array, (long) unsortedArray.length, 4L, comparFunc);
In this example, the arguments of MethodHandle::invoke
correspond to those of the standard C library qsort
function.
Finally, the following statement copies the sorted array values from off-heap to on-heap memory:
// Dereference off-heap memory
sortedArray = array.toArray(ValueLayout.JAVA_INT);
Memory Layouts and Structured Access
Accessing structured data using only basic dereferencing operations can lead to hard-to-read code that's difficult to maintain. Instead, you can use memory layouts to more efficiently initialize and access more complicated native data types such as C structures.
For example, consider the following C declaration, which defines an array of
Point
structures, where each Point
structure has
two members, Point.x
and Point.y
:
struct Point {
int x;
int y;
} pts[10];
You can initialize such a native array as follows:
try (MemorySession session = MemorySession.openConfined()) {
MemorySegment segment =
MemorySegment.allocateNative(2 * 4 * 10, session);
for (int i = 0; i < 10; i++) {
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2), i); // x
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2) + 1, i); // y
}
// ...
}
The MemorySegment::allocateNative method calculates the number of
bytes required for the array. The MemorySegment::setAtIndex method calculates which memory
address offsets to write into each member of a Point
structure. To
avoid these calculations, you can use a memory layout.
To represent the array of Point
structures, the following
example uses a sequence memory layout:
try (MemorySession session = MemorySession.openConfined()) {
SequenceLayout ptsLayout
= MemoryLayout.sequenceLayout(10,
MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")));
VarHandle xHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("x"));
VarHandle yHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("y"));
MemorySegment segment =
MemorySegment.allocateNative(ptsLayout, session);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
xHandle.set(segment, (long) i, i);
yHandle.set(segment, (long) i, i);
}
// ...
}
The first statement creates a sequence memory layout, which is represented
by a SequenceLayout object. It contains a sequence of ten structure
layouts, which are represented by GroupLayout objects. The method MemoryLayout::structLayout returns a
GroupLayout object. Each structure layout contains two JAVA_INT value layouts named x
and
y
:
SequenceLayout ptsLayout
= MemoryLayout.sequenceLayout(10,
MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")));
The predefined value ValueLayout.JAVA_INT contains information about how many bytes a Java int value requires.
The next statements create two memory-access VarHandles that obtain memory address offsets. A VarHandle is a dynamically strongly typed reference to a variable, or to a parametrically-defined family of variables, including static fields, non-static fields, array elements, or components of an off-heap data structure.
VarHandle xHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("x"));
VarHandle yHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("y"));
The method PathElement.sequenceElement() retrieves a memory layout from a
sequence layout. In this example, it retrieves one of the structure layouts from
ptsLayout
. The method call PathElement.groupElement("x") retrieves a memory layout named
x
. You can create a memory layout with a name with the withName(String) method.
The for
statement calls VarHandle::set to dereference memory like MemorySegment::setAtIndex. In this example, it sets a value
(the second argument) at an index (the third argument) in a memory segment (the first
argument). The VarHandles xHandle
and yHandle
know the
size of the Point
structure (8 bytes) and the size of its
int
members (4 bytes). This means you don't have to calculate the
number of bytes required for the array's elements or the memory address offsets like in
the setAtIndex method.
MemorySegment segment =
MemorySegment.allocateNative(ptsLayout, session);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
xHandle.set(segment, (long) i, i);
yHandle.set(segment, (long) i, i);
}
Restricted Methods
Some methods in the Foreign Function and Memory (FFM) API are unsafe and therefore restricted. If used incorrectly, restricted methods can crash the JVM and may silently result in memory corruption.
If you run an application that invokes one of the following restricted methods, the Java
runtime will print a warning message. To suppress this message, add the
--enable-native-access=ALL-UNNAMED
command-line option.
-
static Linker nativeLinker(), SymbolLookup.libraryLookup(String, MemorySession) and SymbolLookup.libraryLookup(Path, MemorySession): These methods are required to create a downcall method handle, which is intrinsically unsafe. A symbol in a foreign library does not typically contain enough signature information, such as arity and the types of foreign function parameters, to enable the linker at runtime to validate linkage requests. When a client interacts with a downcall method handle obtained through an invalid linkage request, for example, by specifying a function descriptor featuring too many argument layouts, the result of such an interaction is unspecified and can lead to JVM crashes.
JVM crashes might occur with upcalls because they are typically invoked in the context of a downcall method handle invocation.
- All dereferencing methods of MemoryAddress: Because a memory address does not feature temporal or spatial bounds, the runtime has no way to check the correctness of the memory dereference operation.
- MemorySegment::ofAddress and VaList::ofAddress: Sometimes it's necessary to turn a memory address obtained from native code into a memory segment with full spatial, temporal and confinement bounds. To do this, clients can obtain a native segment unsafely from a memory address by providing the segment size as well as the segment session. This is a restricted operation because, for instance, an incorrect segment size could result in a JVM crash when attempting to dereference the memory segment.
Calling Native Functions with jextract
The jextract tool mechanically generates
Java bindings from a native library header file. The bindings that this tool generates
depend on the Foreign Function and Memory (FFM) API. With this tool, you don't have to
create downcall and upcall handles for functions you want to invoke; the
jextract
tool generates code that does this for you.
Obtain the source code for jextract from the following site:
https://github.com/openjdk/jextract
This site also contains steps on how to compile and run jextract, additional documentation, and samples.
Run a Python Script in a Java Application
The following steps show you how to generate Java bindings from the Python header
file, Python.h
, then use the generated code to run a Python script in a
Java application. The Python script prints the length of a Java string.
- Run the following command to generate Java bindings for
Python.h
:jextract -l <absolute path of Python shared library> \ --output classes \ -I <directory containing Python header files> \ -t org.python <absolute path of Python.h>
Note:
-
On Linux systems, to obtain the file name of the Python shared library, run the following command. This example assumes that you have Python 3 installed in your system.
ldd $(which python3)
-
On Linux systems, if you can't find
Python.h
or the directory containing the Python header files, you might have to install thepython-devel
package. - If you want to examine the classes and methods that the
jextract
tool creates, run the command with the--source
option. For example, the following command generates the source files of the Java bindings forPython.h
:jextract --source \ --output src \ -I <directory containing Python header files> \ -t org.python <absolute path of Python.h>
-
- In the same directory as
classes
, which should contain the Python Java bindings, create the following file,PythonMain.java
:import java.lang.foreign.MemoryAddress; import java.lang.foreign.MemorySegment; import java.lang.foreign.MemorySession; import static org.python.Python_h.*; import org.python.*; public class PythonMain { public static void main(String[] args) { String myString = "Hello world!"; String script = """ string = "%s" print(string, ': ', len(string), sep='') """.formatted(myString).stripIndent(); Py_Initialize(); try (MemorySession session = MemorySession.openConfined()) { MemorySegment nativeString = session.allocateUtf8String(script); PyRun_SimpleStringFlags( nativeString, MemoryAddress.NULL); Py_Finalize(); } Py_Exit(0); } }
- Compile
PythonMain.java
with the following command:javac --enable-preview -source 19 \ -classpath classes \ PythonMain.java
- Run
PythonMain
with the following command:java -cp classes:. -Djava.library.path=<location of Python shared library> PythonMain
Call qsort Function from Java Application
As mentioned previously, qsort is a C
library function that requires a pointer to a function that compares two elements. The
following steps create Java bindings for the C standard library with
jextract
, create an upcall handle for the comparison function
required by qsort, and then call the qsort function.
- Run the following command to create Java bindings for
stdlib.h
, which is the header file for the C standard library:jextract --output classes -t org.unix <absolute path to stdlib.h>
The generated Java bindings for
stdlib.h
include a Java class namedstdlib_h
, which includes a Java method namedqsort(Addressable, long, long, Addressable)
, and a Java interface named __compar_fn_t, which includes a method named allocate that creates a function pointer for the comparison function required by theqsort
function. To examine the source code of the Java bindings thatjextract
generates, run the tool with the -source option:jextract --source --output src -t org.unix <absolute path to stdlib.h>
- In the same directory where you generated the Java bindings for
stdlib.h
, create the following Java source file, QsortMain.java:import static org.unix.__compar_fn_t.*; import static org.unix.stdlib_h.*; import java.lang.foreign.*; import java.lang.invoke.*; public class QsortMain { public static void main(String[] args) { int[] unsortedArray = new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 }; try (MemorySession session = MemorySession.openConfined()) { // Allocate off-heap memory and store unsortedArray in it MemorySegment array = session.allocateArray( ValueLayout.JAVA_INT, unsortedArray); // Create upcall for comparison function MemorySegment comparFunc = allocate( (addr1, addr2) -> Integer.compare( addr1.get(ValueLayout.JAVA_INT, 0), addr2.get(ValueLayout.JAVA_INT, 0)), session); // Call qsort qsort(array, (long) unsortedArray.length, 4L, comparFunc); // Dereference off-heap memory int[] sortedArray = array.toArray(ValueLayout.JAVA_INT); for (int num : sortedArray) { System.out.print(num + " "); } System.out.println(); } } }
The following statement creates an upcall,
comparFunc
, from a lambda expression:// Create upcall for comparison function MemorySegment comparFunc = allocate( (addr1, addr2) -> Integer.compare( addr1.get(ValueLayout.JAVA_INT, 0), addr2.get(ValueLayout.JAVA_INT, 0)), session);
Consequently, you don't have to create a method handle for the comparison function as described in Upcalls: Passing Java Code as a Function Pointer to a Foreign Function.
- Compile
QsortMain.java
with the following command:javac --enable-preview -source 19 -cp classes QsortMain.java
- Run
QsortMain
with the following command:java --enable-preview -cp classes:. QsortMain