BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Foreign Function & Memory API to Bridge the Gap between Java and Native Libraries

Foreign Function & Memory API to Bridge the Gap between Java and Native Libraries

After its review concluded, JEP 454, Foreign Function & Memory API has been promoted from Targeted to Integrated for JDK 22. This JEP proposes to finalize this feature after two rounds of incubation and three rounds of preview: JEP 412, Foreign Function & Memory API (Incubator), delivered in JDK 17; JEP 419, Foreign Function & Memory API (Second Incubator), delivered in JDK 18; JEP 424, Foreign Function & Memory API (Preview), delivered in JDK 19; JEP 434, Foreign Function & Memory API (Second Preview), delivered in JDK 20; and JEP 442, Foreign Function & Memory API (Third Preview), delivered in the release of JDK 21.

Improvements since the last release include a new Enable-Native-Access manifest attribute that allows code in executable JARs to call restricted methods without the use of the --enable-native-access flag; allow clients to build C function descriptors, avoiding platform-specific constants programmatically; improved support for variable-length arrays in native memory; and support for multiple charsets in native strings.

This API is designed to improve the interaction between Java and native code, offering a more efficient and safer way to access native libraries and manage native memory. The API consists of two main components:

Foreign Function Interface (FFI): This part of the API allows Java programs to call functions written in native languages like C and C++. It abstracts away much of the boilerplate code required in the Java Native Interface (JNI), making it easier to write and maintain code that interacts with native libraries.

Memory Access API: This component provides a set of tools for interacting with native memory. It includes features for memory allocation, deallocation, and manipulation of native data structures. The API also provides safety checks to prevent issues like buffer overflows and common pitfalls when dealing with native code.

The Foreign Function & Memory (FFM) API, part of the java.lang.foreign package, generally consists of a few core classes:

Linker: This interface provides mechanisms to link Java code with foreign functions in libraries that conform to a specific Application Binary Interface (ABI). It supports both downcalls to foreign functions and upcalls from foreign functions to Java code.

SymbolLookup: This interface is used for retrieving the address of a symbol, such as a function or global variable, in a specific library. It supports various types of lookups, including library lookups, loader lookups, and default lookups provided by a Linker.

MemorySegment: This interface provides access to a contiguous region of memory, either on the Java heap ("heap segment") or outside it ("native segment"). It offers various access operations for reading and writing data while ensuring spatial and temporal bounds.

MethodHandle: This class serves as a strongly typed, directly executable reference to an underlying method, constructor, or field. It provides two special invoker methods, invokeExact and invoke, and is immutable with no visible state. A reference of MethodHandle can be obtained via Linker::downcallHandle() to invoke the method.

FunctionDescriptor: A function descriptor models the signature of a foreign function. A function descriptor is made up of zero or more argument layouts and zero or one return layout. A function descriptor is used to create downcall method handles and upcall stubs.

Arena: This interface in Java controls the lifecycle of native memory segments, providing methods for their allocation and deallocation within specified scopes. It comes in different types—global, automatic, confined, and shared—each with unique characteristics regarding lifetime, thread accessibility, and manual control.

For example, here is Java code that obtains a method handle for a C library function radixsort and then uses it to sort four strings which start life in a Java array.

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.util.Arrays;

public class RadixSortExample {
    public static void main(String[] args) {
        RadixSortExample radixSorter = new RadixSortExample();
        String[] javaStrings = {"mouse", "cat", "dog", "car"};

        System.out.println("radixsort input: " + Arrays.toString(javaStrings));

        // Perform radix sort on input array of strings
        javaStrings = radixSorter.sort(javaStrings);

        System.out.println("radixsort output: " + Arrays.toString(javaStrings));
    }

    private String[] sort(String[] strings) {
        // Find foreign function on the C library path
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdlib = linker.defaultLookup();
        MemorySegment radixSort = stdlib.find("radixsort").orElseThrow();
        MethodHandle methodHandle = linker.downcallHandle(radixSort, FunctionDescriptor.ofVoid(
                ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_CHAR
        ));

        // Use try-with-resources to manage the lifetime of off-heap memory
        try (Arena arena = Arena.ofConfined()) {
            // Allocate a region of off-heap memory to store pointers
            MemorySegment pointers = arena.allocateArray(ValueLayout.ADDRESS, strings.length);

            // Copy the strings from on-heap to off-heap
            for (int i = 0; i < strings.length; i++) {
                MemorySegment cString = arena.allocateUtf8String(strings[i]);
                pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
            }

            // Sort the off-heap data by calling the foreign function
            methodHandle.invoke(pointers, strings.length, MemorySegment.NULL, '\0');

            // Copy the (reordered) strings from off-heap to on-heap
            for (int i = 0; i < strings.length; i++) {
                MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
                cString = cString.reinterpret(Long.MAX_VALUE);
                strings[i] = cString.getUtf8String(0);
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        return strings;
    }
}

To be able to run the above code, the developer needs to install JDK 22 which can be easily downloadable through SDKman.

Traditionally, handling off-heap memory in Java has been a challenge. Before, Java developers were confined to using ByteBuffer objects for off-heap memory operations. However, the FFM API introduces MemorySegment objects, permitting more control over the allocation and deallocation of off-heap memory. Moreover, MemorySegment::asByteBuffer and MemorySegment::ofBuffer methods further strengthen the bridge between traditional byte buffers and the new memory segment objects.

The FFM API aligns with the java.nio.channels API, providing a deterministic way to deallocate off-heap byte buffers. This negates the need to rely on non-standard, non-deterministic techniques, such as invoking sun.misc.Unsafe::invokeCleaner, paving the way for a more reliable and standardized approach to memory management.

The enhancements in the FFM API are a step towards making the Java platform safer, more efficient, and interoperable. The focus is not only on facilitating Java-native interactions, but also on safeguarding them. The API provides a more seamless, Java-idiomatic approach to working with native libraries, offering a solid alternative to JNI's complexities and safety issues.


The API is expected to revolutionize how Java interacts with native libraries, and it aligns with the broader Java roadmap that aims to make the platform safer and more efficient out-of-the-box. For developers working with Java and native libraries, this is an exciting development that promises to simplify complexities while ensuring safer and more efficient code.

About the Author

Rate this Article

Adoption
Style

BT