JDK-8310626 : JEP 454: Foreign Function & Memory API
  • Type: JEP
  • Component: core-libs
  • Sub-Component: java.lang.foreign
  • Priority: P2
  • Status: Closed
  • Resolution: Delivered
  • Fix Versions: 22
  • Submitted: 2023-06-22
  • Updated: 2024-01-29
  • Resolved: 2024-01-29
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8318304 :  
Description
Summary
-------

Introduce an API by which Java programs can interoperate with code and data outside of the Java runtime. By efficiently invoking foreign functions (i.e., code outside the JVM), and by safely accessing foreign memory (i.e., memory not managed by the JVM), the API enables Java programs to call native libraries and process native data without the brittleness and danger of JNI.


History
-------

The Foreign Function & Memory (FFM) API was originally proposed as a preview feature by [JEP 424](https://openjdk.org/jeps/424) (JDK 19) and subsequently refined by JEPs [JEP 434](https://openjdk.org/jeps/434) (JDK 20) and [JEP 442](https://openjdk.org/jeps/442) (JDK 21). This JEP proposes to finalize the FFM API with further small refinements based upon continued experience and feedback. In this version we have:

- Provided a new linker option allowing clients to pass heap segments to downcall method handles;
- Introduced the `Enable-Native-Access` JAR-file manifest attribute, allowing code in executable JAR files to call restricted methods without having to use the `--enable-native-access` command-line option;
- Enabled clients to build C-language function descriptors programmatically, avoiding platform-specific constants;
- Improved support for variable-length arrays in native memory; and
- Added support for arbitrary charsets for native strings.

## Goals

- *Productivity* — Replace the brittle machinery of `native` methods and the [Java Native Interface] (JNI) with a concise, readable, and pure-Java API.

- *Performance* — Provide access to foreign functions and memory with overhead comparable to, if not better than, JNI and `sun.misc.Unsafe`.

- *Broad platform support* — Enable the discovery and invocation of native libraries on every platform where the JVM runs.

- *Uniformity* — Provide ways to operate on structured and unstructured data, of unlimited size, in multiple kinds of memory (e.g., native memory, persistent memory, and managed heap memory).

- *Soundness* —  Guarantee no use-after-free bugs, even when memory is allocated and deallocated across multiple threads.

- *Integrity* — Allow programs to perform unsafe operations with native code and data, but warn users about such operations by default.

[Java Native Interface]: https://docs.oracle.com/en/java/javase/21/docs/specs/jni/index.html


## Non-goals

It is not a goal to

- Re-implement JNI on top of this API, or otherwise change JNI in any way;
- Re-implement legacy Java APIs, such as `sun.misc.Unsafe`, on top of this API;
- Provide tooling that mechanically generates Java code from native-code header files; or
- Change how Java applications that interact with native libraries are packaged and deployed (e.g., via multi-platform JAR files).


## Motivation

The Java Platform has always offered a rich foundation to library and application developers who wish to reach beyond the JVM and interact with other platforms. Java APIs expose non-Java resources conveniently and reliably, whether to access remote data (JDBC), invoke web services (HTTP client), serve remote clients (NIO channels), or communicate with local processes (Unix-domain sockets). Unfortunately, Java developers still face significant obstacles in accessing an important kind of non-Java resource: code and data on the same machine as the JVM, but outside the Java runtime.

### Foreign memory

Objects created via the `new` keyword are stored in the JVM's *heap*, where they are subject to garbage collection when no longer needed. However, the cost and unpredictability of garbage collection is unacceptable for performance-critical libraries such as  [Tensorflow](https://github.com/tensorflow/tensorflow), [Ignite](https://ignite.apache.org), [Lucene](https://lucene.apache.org/), and [Netty](https://netty.io). They need to store data outside the heap, in *off-heap memory* which they allocate and deallocate themselves. Access to off-heap memory also allows data to be serialized and deserialized by mapping files directly into memory via, e.g., [`mmap`](https://en.wikipedia.org/wiki/Mmap).

The Java Platform has historically provided two APIs for accessing off-heap memory:

* The [`ByteBuffer` API](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/nio/ByteBuffer.html) provides *direct* byte buffers, which are Java objects backed by fixed-size regions of off-heap memory. However, the maximum size of a region is limited to two gigabytes and the methods for reading and writing memory are rudimentary and error-prone, providing little more than indexed access to primitive values. More seriously, the memory which backs a direct byte buffer is deallocated only when the buffer object is garbage collected, which developers cannot control.

* The [`sun.misc.Unsafe` API](https://github.com/openjdk/jdk/blob/master/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java) provides low-level access to on-heap memory that also works for off-heap memory. Using `Unsafe` is fast (because its memory access operations are intrinsic to the JVM), allows huge off-heap regions (theoretically up to 16 exabytes), and offers fine-grained control over deallocation (because `Unsafe::freeMemory` can be called at any time). However, this programming model is weak because it gives developers too much control. A library in a long-running  application can allocate and interact with multiple regions of off-heap memory over time; data in one region can point to data in another region, and regions must be deallocated in the correct order or else dangling pointers will cause use-after-free bugs.

  (The same criticism applies to APIs outside the JDK that offer fine-grained allocation and deallocation by wrapping native code which calls `malloc` and `free`.)

In summary, sophisticated developers deserve an API that can allocate, manipulate, and share off-heap memory with the same fluidity and safety as on-heap memory. Such an API should balance the need for predictable deallocation with the need to prevent premature deallocation, which can lead to JVM crashes or, worse, to silent memory corruption.

### Foreign functions

JNI has supported the invocation of native code (i.e., foreign functions) since Java 1.1, but it is inadequate for many reasons.

- JNI involves several tedious artifacts: a Java API (`native` methods), a C header file derived from the Java API, and a C implementation that calls the native library of interest. Java developers must work across multiple toolchains to keep platform-dependent artifacts in sync, which is especially burdensome when the native library evolves rapidly.

- JNI can only interoperate with libraries written in languages, typically C and C++, that use the calling convention of the operating system and CPU for which the JVM was built.  A `native` method cannot be used to invoke a function written in a language that uses a different convention.

- JNI does not reconcile the Java type system with the C type system. Java code represents aggregate data with objects, but C code represents aggregate data with structs, so any Java object passed to a `native` method must be laboriously unpacked by native code. For example, consider a Java record class `Person`: Passing a `Person` object to a `native` method requires the native code to use JNI's C API to extract fields (e.g., `firstName` and `lastName`) from the object. As a result, Java developers sometimes flatten their data into a single object (e.g., a byte array or a direct byte buffer) but more often, since passing Java objects via JNI is slow, they use the `Unsafe` API to allocate off-heap memory and pass its address to a `native` method as a `long` — which makes the Java code tragically unsafe!

Over the years, numerous frameworks have emerged to fill the gaps left by JNI, including [JNA](https://github.com/java-native-access/jna), [JNR](https://github.com/jnr/jnr-ffi) and [JavaCPP](https://github.com/bytedeco/javacpp). These frameworks are often a marked improvement over JNI but the situation is still less than ideal — especially when compared with languages which offer first-class native interoperation. For example, Python's [ctypes](https://docs.python.org/3/library/ctypes.html) package can dynamically wrap functions in native libraries without any glue code. Other languages, such as [Rust](https://rust-lang.github.io/rust-bindgen/), provide tools which mechanically derive native wrappers from C/C++ header files.

Ultimately, Java developers should have a supported API that enables them to straightforwardly consume any native library deemed useful for a particular task, without the tedious glue and clunkiness of JNI. Two excellent abstractions to build upon are _[method handles]_, which are direct references to method-like entities, and _[variable handles]_, which are direct references to variable-like entities. Exposing native code via method handles, and native data via variable handles, would radically simplify the task of writing, building, and distributing Java libraries which depend upon native libraries. Furthermore, an API capable of modeling foreign functions (i.e., native code) and foreign memory (i.e., off-heap data) would provide a solid foundation for third-party native interoperation frameworks.

[method handles]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/MethodHandle.html
[variable handles]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/VarHandle.html


## Description

The Foreign Function & Memory API (FFM API) defines classes and interfaces so that client code in libraries and applications can

- Control the allocation and deallocation of foreign memory<br/>([`MemorySegment`], [`Arena`], and [`SegmentAllocator`]),
- Manipulate and access structured foreign memory<br/>([`MemoryLayout`] and [`VarHandle`]), and
- Call foreign functions ([`Linker`], [`SymbolLookup`], [`FunctionDescriptor`], and [`MethodHandle`]).

The FFM API resides in the [`java.lang.foreign`] package of the `java.base` module.

### Example

As a brief example of using the FFM API, 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 (a few details are elided).

```
// 1. Find foreign function on the C library path
Linker linker          = Linker.nativeLinker();
SymbolLookup stdlib    = linker.defaultLookup();
MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. Use try-with-resources to manage the lifetime of off-heap memory
try (Arena offHeap = Arena.ofConfined()) {
    // 4. Allocate a region of off-heap memory to store four pointers
    MemorySegment pointers
        = offHeap.allocate(ValueLayout.ADDRESS, javaStrings.length);
    // 5. Copy the strings from on-heap to off-heap
    for (int i = 0; i < javaStrings.length; i++) {
        MemorySegment cString = offHeap.allocateFrom(javaStrings[i]);
        pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
    }
    // 6. Sort the off-heap data by calling the foreign function
    radixsort.invoke(pointers, javaStrings.length, MemorySegment.NULL, '\0');
    // 7. Copy the (reordered) strings from off-heap to on-heap
    for (int i = 0; i < javaStrings.length; i++) {
        MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
        javaStrings[i] = cString.reinterpret(...).getString(0);
    }
} // 8. All off-heap memory is deallocated here
assert Arrays.equals(javaStrings,
                     new String[] {"car", "cat", "dog", "mouse"});  // true
```

This code is far clearer than any solution that uses JNI, since the implicit conversions and memory accesses that would have been hidden behind `native` method calls are now expressed directly in Java code. Modern Java idioms can also be used; for example, streams can allow multiple threads to copy data between on-heap and off-heap memory in parallel.

### Memory segments and arenas

A [*memory segment*][`MemorySegment`] is an abstraction backed by a contiguous region of memory, located either off-heap or on-heap. A memory segment can be

- A _native_ segment, allocated from scratch in off-heap memory (as if via `malloc`),
- A _mapped_ segment, wrapped around a region of mapped off-heap memory (as if via `mmap`), or
- An _array_ or _buffer_ segment, wrapped around a region of on-heap memory associated with an existing Java array or byte buffer, respectively.

All memory segments provide spatial and temporal bounds which ensure that memory access operations are safe. In a nutshell, the bounds guarantee no use of unallocated memory and no use-after-free.

The *spatial bounds* of a segment determine the range of memory addresses associated with the segment. For example, the code below allocates a native segment of 100 bytes, so the associated range of addresses is from some *base address* `b` to `b + 99` inclusive.

```
MemorySegment data = Arena.global().allocate(100);
```

The *temporal bounds* of a segment determine its lifetime, that is, the period until the region of memory which backs the segment is deallocated. The FFM API guarantees that a memory segment cannot be accessed after its backing region of memory is deallocated.

The temporal bounds of a segment are determined by the [_arena_][`Arena`] used to allocate the segment. Multiple segments allocated in the same arena have the same temporal bounds, and can safely contain mutual references: Segment `A` can hold a pointer to an address in segment `B`, and segment `B` can hold a pointer to an address in segment `A`, and both segments will be deallocated at the same time so that neither segment has a dangling pointer.

The simplest arena is the [*global* arena][`Arena::global`], which provides an unbounded lifetime: It is always alive. A segment allocated in the global arena, as in the code above, is always accessible and the region of memory backing the segment is never deallocated.

Most programs, though, require off-heap memory to be deallocated while the program is running, and thus need memory segments with bounded lifetimes.

An [*automatic* arena][`Arena::ofAuto`] provides a bounded lifetime: A segment allocated by an automatic arena can be accessed until the JVM's garbage collector detects that the memory segment is unreachable, at which point the region of memory backing the segment is deallocated. For example, this method allocates a segment in an automatic arena:

```
void processData() {
    MemorySegment data = Arena.ofAuto().allocate(100);
    ... use the 'data' variable ...
    ... use the 'data' variable some more ...
}  // the region of memory backing the 'data' segment
   // is deallocated here (or later)
```

As long as the `data` variable does not leak out of the method, the segment will eventually be detected as unreachable and its backing region will be deallocated.

An automatic arena's bounded but non-deterministic lifetime is not always sufficient. For example, an API that maps a memory segment from a file should allow the client to deterministically deallocate the region of memory backing the segment since waiting for the garbage collector to do so could adversely affect performance.

A [*confined* arena][`Arena::ofConfined`] provides a bounded and deterministic lifetime: It is alive from the time the client opens the arena until the time the client closes the arena. A memory segment allocated in a confined arena can be accessed only before the arena is closed, at which point the region of memory backing the segment is deallocated. Attempts to access a memory segment after its arena is closed will fail with an exception. For example, this code opens an arena and uses the arena to allocate two segments:

```
MemorySegment input = null, output = null;
try (Arena processing = Arena.ofConfined()) {
    input = processing.allocate(100);
    ... set up data in 'input' ...
    output = processing.allocate(100);
    ... process data from 'input' to 'output' ...
    ... calculate the ultimate result from 'output' and store it elsewhere ...
}  // the regions of memory backing the segments are deallocated here
...
input.get(ValueLayout.JAVA_BYTE, 0);  // throws IllegalStateException
                                      // (also for 'output')
```

Exiting the `try`-with-resources block closes the arena, at which point all segments allocated by the arena are invalidated atomically and the regions of memory backing the segments are deallocated.

A confined arena's deterministic lifetime comes at a price: Only one thread can access the memory segments allocated in a confined arena. If multiple threads need access to a segment then a [*shared* arena][`Arena::ofShared`] can be used. The memory segments allocated in a shared arena can be accessed by multiple threads, and any thread — whether it accesses the region or not — can close the arena to deallocate the segments. Closing the arena atomically invalidates the segments, though the deallocation of the regions of memory backing the segments might not occur immediately since an expensive synchronization operation is needed to detect and cancel pending concurrent access operations on the segments.

In summary, an arena controls which threads can access a memory segment, and when, in order to provide both strong temporal safety and a predictable performance model. The FFM API offers a choice of arenas so that developers can trade off breadth of access against timeliness of deallocation.

### Dereferencing segments

To dereference some data in a memory segment we need to take into account several factors:

* The number of bytes to be dereferenced,
* The alignment constraints of the address at which dereference occurs,
* The endianness with which bytes are stored in the memory segment, and
* The Java type to be used in the dereference operation (e.g., `int` vs `float`).

All these characteristics are captured in the [`ValueLayout`] abstraction. For example, the predefined `JAVA_INT` value layout is four bytes wide, is aligned on four-byte boundaries, uses the native platform endianness (e.g., little-endian on Linux/x64), and is associated with the Java type `int`.

Memory segments have simple dereference methods to read values from and write values to memory segments. These methods accept a value layout, which specifies the properties of the dereference operation. For example, we can write 25 `int` values at consecutive offsets in a memory segment:

```
MemorySegment segment
    = Arena.ofAuto().allocate(100,                                   // size
                              ValueLayout.JAVA_INT.byteAlignment()); // alignment
for (int i = 0; i < 25; i++) {
    segment.setAtIndex(ValueLayout.JAVA_INT,
                       /* index */ i,
                       /* value to write */ i);
}
```

### Memory layouts and structured access

Consider the following C declaration, which defines an array of ten `Point` structs, where each `Point` struct has two members:

```
struct Point {
    int x;
    int y;
} pts[10];
```

Using the methods shown in the previous section, we could allocate native memory for the array and initialize each of the ten `Point` structs with the following code (we assume that `sizeof(int) == 4`):

```
MemorySegment segment
    = Arena.ofAuto().allocate(2 * ValueLayout.JAVA_INT.byteSize() * 10, // size
                              ValueLayout.JAVA_INT.byteAlignment());    // alignment
for (int i = 0; i < 10; i++) {
    segment.setAtIndex(ValueLayout.JAVA_INT,
                       /* index */ (i * 2),
                       /* value to write */ i); // x
    segment.setAtIndex(ValueLayout.JAVA_INT,
                       /* index */ (i * 2) + 1,
                       /* value to write */ i); // y
}
```

To reduce the need for tedious calculations about memory layout (e.g., `(i * 2) + 1` in the example above), we can use a [`MemoryLayout`] to describe the content of a memory segment in a more declarative fashion. A native memory segment that contains ten structs, each of which is a pair of ints, is described by a _[sequence layout]_ that contains ten occurrences of a _[struct layout]_, each of which is a pair of `JAVA_INT` layouts:

```
SequenceLayout ptsLayout
    = MemoryLayout.sequenceLayout(10,
                                  MemoryLayout.structLayout(
                                      ValueLayout.JAVA_INT.withName("x"),
                                      ValueLayout.JAVA_INT.withName("y")));
```

From the sequence layout we can obtain a variable handle that can get and set a data element in any memory segment with the same layout. One kind of element that we wish to set is the member called `x` in an arbitrary struct in the sequence-of-structs. Accordingly, we obtain a variable handle for such elements by providing a _[layout path]_ that navigates to a struct and then to its member `x`:

```
VarHandle xHandle = ptsLayout.varHandle(PathElement.sequenceElement(),
                                        PathElement.groupElement("x"));
```

Correspondingly, for the member `y`:

```
VarHandle yHandle = ptsLayout.varHandle(PathElement.sequenceElement(),
                                        PathElement.groupElement("y"));
```

We can now allocate and initialize an array of ten `Point` structs by allocating a native segment with the sequence-of-structs layout and then setting the two members in each successive struct via the two variable handles. Each handle accepts the `MemorySegment` to manipulate, the base address of the sequence-of-structs within the segment, and an index denoting which struct in the sequence-of-structs is to have its member set.

```
MemorySegment segment = Arena.ofAuto().allocate(ptsLayout);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
    xHandle.set(segment,
                /* base */ 0L,
                /* index */ (long) i,
                /* value to write */ i); // x
    yHandle.set(segment,
                /* base */ 0L,
                /* index */ (long) i,
                /* value to write */ i); // y
}
```

[layout path]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/MemoryLayout.html#layout-paths
[sequence layout]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/SequenceLayout.html
[struct layout]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/StructLayout.html

### Segment allocators

Memory allocation is often a bottleneck when clients use off-heap memory. The FFM API therefore includes a [`SegmentAllocator`] abstraction to define operations to allocate and initialize memory segments. As a convenience, the [`Arena`] class implements the `SegmentAllocator` interface so that arenas can be used to allocate native segments from a variety of existing sources. In other words, `Arena` is a "one stop shop" for flexible allocation and timely deallocation of off-heap memory:

```
try (Arena offHeap = Arena.ofConfined()) {
    MemorySegment nativeInt       = offHeap.allocateFrom(ValueLayout.JAVA_INT, 42);
    MemorySegment nativeIntArray  = offHeap.allocateFrom(ValueLayout.JAVA_INT,
                                                      0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    MemorySegment nativeString    = offHeap.allocateFrom("Hello!");
   ...
} // memory released here
```

Segment allocators can also be obtained via factories in the [`SegmentAllocator`] interface. For example, one factory creates a [*slicing allocator*][`SegmentAllocator::slicingAllocator`] that responds to allocation requests by returning memory segments which are part of a previously allocated segment; thus, many requests can be satisfied without physically allocating more memory. The following code obtains a slicing allocator over an existing segment and then uses it to allocate a segment initialized from a Java array:

```
MemorySegment segment = ...
SegmentAllocator allocator = SegmentAllocator.slicingAllocator(segment);
for (int i = 0 ; i < 10 ; i++) {
    MemorySegment s = allocator.allocateFrom(ValueLayout.JAVA_INT, 1, 2, 3, 4, 5);
    ...
}
```

Segment allocators can be used as building blocks to create arenas that support custom allocation strategies. For example, if a large number of native segments will share the same bounded lifetime then a custom arena could use a slicing allocator to allocate the segments efficiently. This lets developers enjoy both scalable allocation (thanks to slicing) and deterministic deallocation (thanks to the arena).

As an example, the following code defines a *slicing arena* that behaves like a confined arena  but internally uses a slicing allocator to respond to allocation requests. When the slicing arena is closed, the underlying confined arena is closed, invalidating all segments allocated in the slicing arena. (Some details are elided.)

```
class SlicingArena implements Arena {
     final Arena arena = Arena.ofConfined();
     final SegmentAllocator slicingAllocator;

     SlicingArena(long size) {
         slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size));
     }

     public void allocate(long byteSize, long byteAlignment) {
         return slicingAllocator.allocate(byteSize, byteAlignment);
    }

     public void close() {
         return arena.close();
     }
}
```

The earlier code which used a slicing allocator directly can now be written more succinctly:

```
try (Arena slicingArena = new SlicingArena(1000)) {
     for (int i = 0 ; i < 10 ; i++) {
         MemorySegment s = slicingArena.allocateFrom(ValueLayout.JAVA_INT, 1, 2, 3, 4, 5);
         ...
     }
} // all memory allocated is released here
```

### Looking up foreign functions

The first ingredient of any support for foreign functions is a mechanism to find the address of a given symbol in a loaded native library. This capability, represented by a [`SymbolLookup`] object, is crucial for linking Java code to foreign functions (see [below](#Linking-Java-code-to-foreign-functions)). The FFM API supports three different kinds of symbol lookup objects:

 - `SymbolLookup::libraryLookup(String, Arena)` creates a *library lookup*, which locates all the symbols in a user-specified native library. Creating the lookup object causes the library to be loaded (e.g., using `dlopen()`) and associated with a `Arena` object. The library is unloaded (e.g., using `dlclose()`) when the provided arena is closed.

 - `SymbolLookup::loaderLookup()` creates a *loader lookup*, which locates all the symbols in all the native libraries that have been loaded by classes in the current class loader using the `System::loadLibrary` and `System::load` methods.

 - `Linker::defaultLookup()` creates a *default lookup*, which locates all the symbols in libraries that are commonly used on the native platform (i.e., operating system and processor) associated with the [`Linker`] instance.

Given a symbol lookup object, a client can find a foreign function with the `SymbolLookup::find(String)` method. If the named function is present among the symbols seen by the  symbol lookup then the method returns a zero-length memory segment (see [below](#Zero-length-memory-segments)) whose base address points to the function's entry point. For example, the following code uses a  loader lookup to load the OpenGL library and find the address of its `glGetString` function:

```
try (Arena arena = Arena.ofConfined()) {
    SymbolLookup opengl = SymbolLookup.libraryLookup("libGL.so", arena);
    MemorySegment glVersion = opengl.find("glGetString").get();
    ...
} // libGL.so unloaded here
```

`SymbolLookup::libraryLookup(String, Arena)` differs from JNI's library loading mechanism (i.e., `System::loadLibrary`) in an important way. Native libraries designed to work with JNI can use JNI functions to perform Java operations, such as object allocation or method access, which involve [class loading](https://docs.oracle.com/en/java/javase/21/docs/specs/jni/functions.html#findclass). Therefore such libraries must be associated with a class loader when they are loaded by the JVM. Then, to preserve [class loader integrity](https://docs.oracle.com/en/java/javase/21/docs/specs/jni/invocation.html#library-and-version-management), the same JNI-using library cannot be loaded from classes defined in different class loaders.

In contrast, the FFM API does not offer functions for native code to access the Java environment, and does not assume that native libraries are designed to work with the FFM API. Native libraries loaded via `SymbolLookup::libraryLookup(String, Arena)` were not necessarily written to be accessed from Java code, and make no attempt to perform Java operations. As such, they are not tied to a particular class loader and can be (re)loaded as many times as needed by FFM API clients in different loaders.

### Linking Java code to foreign functions

The [`Linker`] interface is the core of how Java code interoperates with native code. While in this document we often refer to interoperation between Java code and C libraries, the concepts in this interface are general enough to support other, non-Java languages in future. The `Linker` interface enables both *downcalls* (calls from Java code to native code) and *upcalls* (calls from native code back to Java code).

```
interface Linker {
    MethodHandle downcallHandle(MemorySegment address,
                                FunctionDescriptor function);
    MemorySegment upcallStub(MethodHandle target,
                          FunctionDescriptor function,
                          Arena arena);
}
```

For downcalls, the `downcallHandle` method takes the address of a foreign function — typically, a `MemorySegment` obtained from a library lookup — and exposes the foreign function as a *downcall method handle*. Later, Java code invokes the downcall method handle by calling its `invoke` (or `invokeExact`) method, and the foreign function runs. Any arguments passed to the method handle's `invoke` method are passed on to the foreign function.

For upcalls, the `upcallStub` method takes a method handle — typically, one which refers to a Java method, rather than a downcall method handle — and converts it to a `MemorySegment` instance. Later, the memory segment is passed as an argument when Java code invokes a downcall method handle. In effect, the memory segment serves as a function pointer. (For more information on upcalls, see [below](#Upcalls).)

Clients link to C functions using the *native linker*, which they obtain via `Linker::nativeLinker()`. The native linker is an implementation of the `Linker` interface that conforms to the Application Binary Interface (ABI) of the native platform on which the JVM is running. The ABI specifies the _calling convention_ that enables code written in one language to pass arguments to code written in another language and receive a result. The ABI also specifies the size, alignment, and endianness of scalar C types, how variadic calls should be handled, and other details. While the `Linker` interface is neutral with respect to calling conventions, the  native linker is optimized for the calling conventions of many platforms:

 - Linux/x64
 - Linux/AArch64
 - Linux/RISC-V
 - Linux/PPC64
 - Linux/s390
 - macOS/x64
 - macOS/AArch64
 - Windows/x64
 - Windows/AArch64
 - AIX/ppc64

The native linker supports the calling conventions of [other platforms](https://github.com/libffi/libffi#supported-platforms) by delegating to [`libffi`].

<a id="strlen">As</a> an example, suppose we wish to downcall from Java code to the `strlen` function defined in the standard C library:

```
size_t strlen(const char *s);
```

A downcall method handle that exposes `strlen` is obtained as follows (the details of `FunctionDescriptor` will be described shortly):

```
Linker linker = Linker.nativeLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.defaultLookup().find("strlen").get(),
    FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);
```

Invoking the downcall method handle runs `strlen` and makes its result available to Java code:

```
try (Arena arena = Arena.ofConfined()) {
    MemorySegment str = arena.allocateFrom("Hello");
    long len          = (long)strlen.invoke(str);    // 5
}
```

For the argument to `strlen` we use one of the `allocateFrom` helper methods of `Arena` to convert a Java string into an off-heap memory segment. Passing this memory segment to `strlen.invoke` causes the base address of the memory segment to be passed to the `strlen` function as the `char *` argument.

Method handles work well for exposing foreign functions because the JVM already optimizes the invocation of method handles all the way down to native code. When a method handle refers to a method in a `class` file, invoking the method handle typically causes the target method to be JIT-compiled; subsequently, the JVM interprets the Java bytecode that calls `MethodHandle::invokeExact` by transferring control to the assembly code generated for the target method. Thus, a traditional method handle in Java targets non-Java code behind the scenes; a downcall method handle is a natural extension that lets developers target non-Java code explicitly. Method handles also enjoy a property called [_signature polymorphism_](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/MethodHandle.html#sigpoly) which allows box-free invocation with primitive arguments. In sum, method handles let the `Linker` expose foreign functions in a natural, efficient, and extensible manner.

### Describing C types in Java code

To create a downcall method handle, the native linker requires the client to provide a [`FunctionDescriptor`] that describes the C parameter types and C return type of the target C function. C types are described by [`MemoryLayout`] objects, principally [`ValueLayout`], for scalar C types such as `int` and `float`, and [`StructLayout`], for C struct types.  The memory layout associated with a C struct type must be a composite layout which defines the sub-layouts for all the fields in the C struct, including any platform-dependent padding a native compiler might insert.

The native linker uses the `FunctionDescriptor` to derive the [type](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/MethodHandle.html#type()) of the downcall method handle. Every method handle is strongly typed, which means it is stringent about the number and types of the arguments that can be passed to its `invokeExact` method at run time. For example, a method handle created to take one `MemorySegment` argument cannot be invoked via `invokeExact(<MemorySegment>, <MemorySegment>)`, even though `invokeExact` is a varargs method. The type of the downcall method handle describes the Java signature which developers must use when invoking the downcall method handle. It is, effectively, the Java-level view of the C function.

Developers must be aware of the current native platform if they target C functions that use scalar types such as `long`, `int`, and `size_t`. This is because the association of scalar C types with predefined value layouts varies by platform. The current platform's association between scalar C types and `JAVA_*` value layouts is exposed by `Linker::canonicalLayouts()`.

As an example, suppose a downcall method handle should expose a C function that takes a C `int` and returns a C `long`:

- On Linux/x64 and macOS/x64, the C types `long` and `int` are associated with the predefined layouts `JAVA_LONG` and `JAVA_INT` respectively, so the required `FunctionDescriptor` can be obtained via `FunctionDescriptor.of(JAVA_LONG, JAVA_INT)`. The native linker then arranges for the type of the downcall method handle to be the Java signature `int` to `long`.

- On Windows/x64, the C type `long` is associated with the predefined layout `JAVA_INT`, so the required `FunctionDescriptor` must be obtained with `FunctionDescriptor.of(JAVA_INT, JAVA_INT)`. The native linker then arranges for the type of the downcall method handle to be the Java signature `int` to `int`.

Developers can target C functions that use pointers without being aware of the current native platform or the size of pointers on the current platform. On all platforms, a C pointer type is associated with the predefined layout `ADDRESS`, whose size is determined at run time. Developers do not need to distinguish between C pointer types such as `int*` and `char**`.

As an example, suppose a downcall method handle should expose a `void` C function that takes a pointer. Since every C pointer type is associated with the layout `ADDRESS`, the required `FunctionDescriptor` can be obtained with `FunctionDescriptor.ofVoid(ADDRESS)`. The native linker then arranges for the type of the downcall method handle to be the Java signature `MemorySegment` to `void`. When a `MemorySegment` is passed to the downcall method handle, the base address of the segment will be passed to the target C function.

Finally, unlike JNI, the native linker supports passing structured data to foreign functions. Suppose a downcall method handle should expose a `void` C function that takes a struct described by this layout:

```
MemoryLayout SYSTEMTIME  = MemoryLayout.ofStruct(
  JAVA_SHORT.withName("wYear"),      JAVA_SHORT.withName("wMonth"),
  JAVA_SHORT.withName("wDayOfWeek"), JAVA_SHORT.withName("wDay"),
  JAVA_SHORT.withName("wHour"),      JAVA_SHORT.withName("wMinute"),
  JAVA_SHORT.withName("wSecond"),    JAVA_SHORT.withName("wMilliseconds")
);
```

The required `FunctionDescriptor` can be obtained with `FunctionDescriptor.ofVoid(SYSTEMTIME)`. The native linker will arrange for the type of the downcall method handle to be the Java signature `MemorySegment` to `void`.

Given the calling convention of the native platform, the native linker uses the `FunctionDescriptor` to determine how the struct's fields should be passed to the C function when a downcall method handle is invoked with a `MemorySegment` argument. For one calling convention, the native linker could arrange to decompose the incoming memory segment, pass the first four fields using general CPU registers, and pass the remaining fields on the C stack. For another calling convention, the native linker could arrange to pass the struct indirectly by allocating a region of memory, bulk-copying the contents of the incoming memory segment into that region, and passing a pointer to that region to the C function. This low level packaging of arguments happens behind the scenes, without any supervision by client code.

If a C function returns a by-value struct (not shown here) then a fresh memory segment must be allocated off-heap and returned to the Java client. To achieve this, the method handle returned by `downcallHandle` requires an additional `SegmentAllocator` argument which the native linker uses to allocate a memory segment to hold the struct returned by the C function.

As mentioned earlier, while the native linker is focused on providing interoperation between Java code and C libraries, the `Linker` interface is language-neutral: It does not specify how any native data types are defined, so developers are responsible for obtaining suitable layout definitions for C types. This choice is deliberate, since layout definitions for C types — whether simple scalars or complex structs — are ultimately platform-dependent. We expect that in practice such layouts will be mechanically generated by tools that are specific to target native platforms.

### Zero-length memory segments

Foreign functions often allocate a region of memory and return a pointer to that region. Modeling such a region with a memory segment is challenging because the region's size is not available to the Java runtime. For example, a C function with return type `char*` might return a pointer to a region containing a single `char` value or to a region containing a sequence of `char` values terminated by `'\0'`. The size of the region is not readily apparent to the code calling the foreign function.

The FFM API represents a pointer returned from a foreign function as a _zero-length memory segment_. The address of the segment is the value of the pointer, and the size of the segment is zero. Similarly, when a client reads a pointer from a memory segment then a zero-length memory segment is returned.

A zero-length segment has trivial spatial bounds, so any attempt to access such a segment fails with `IndexOutOfBoundsException`. This is a crucial safety feature: Since these segments are associated with a region of memory whose size is not known, access operations involving these segments cannot be validated. In effect, a zero-length memory segment wraps an address, and it cannot be used without explicit intent.

Clients can turn a zero-length memory segment into a native segment of a specific size via the method [`MemorySegment::reinterpret`]. This method attaches fresh spatial and temporal bounds to a zero-length memory segment in order to allow dereference operations. The memory segment returned by this method is unsafe: A zero-length memory segment might be backed by a region of memory that is 10 bytes long, but the client might overestimate the size of the region and use `MemorySegment::reinterpret` to obtain a segment that is 100 bytes long. Later, this might result in attempts to dereference memory outside the bounds of the region, which might cause a JVM crash or — even worse — result in silent memory corruption.

Because overriding the spatial and temporal bounds of a zero-length memory segment is unsafe, the `MemorySegment::reinterpret` method is *restricted*. Using it in a program causes the Java runtime to, by default, issue warnings (see more [below](#Safety)).

### Upcalls

Sometimes it is useful to pass Java code as a function pointer to some foreign function. We can do that by using the `Linker` support for upcalls. In this section we build, piece by piece, a more sophisticated example which demonstrates the full power of the `Linker`, with full bidirectional interoperation of both code and data across the Java/native boundary.

Consider this function defined in the standard C library:

```
void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));
```

To call `qsort` from Java code, we first need to create a downcall method handle:

```
Linker linker = Linker.nativeLinker();
MethodHandle qsort = linker.downcallHandle(
    linker.defaultLookup().find("qsort").get(),
    FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS)
);
```

As before, we use the `JAVA_LONG` layout to map the C `size_t` type, and we use the `ADDRESS` layout for both the first pointer parameter (the array pointer) and the last parameter (the function pointer).

`qsort` sorts the contents of an array using a custom comparator function, `compar`, passed as a function pointer. Therefore, to invoke the downcall method handle we need a function pointer to pass as the last parameter to the method handle's `invokeExact` method. `Linker::upcallStub` helps us create function pointers by using existing method handles, as follows.

First, we write a `static` method that compares two `int` values, represented indirectly as `MemorySegment` objects:

```
class Qsort {
    static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
        return Integer.compare(elem1.get(JAVA_INT, 0), elem2.get(JAVA_INT, 0));
    }
}
```

Second, we create a method handle pointing to the Java comparator method:

```
MethodHandle comparHandle
    = MethodHandles.lookup()
                   .findStatic(Qsort.class, "qsortCompare",
                               MethodType.methodType(int.class,
                                                     MemorySegment.class,
                                                     MemorySegment.class));
```

Third, now that we have a method handle for our Java comparator we can create a function pointer using `Linker::upcallStub`. Just as for downcalls, we describe the signature of the function pointer using a `FunctionDescriptor`:

```
MemorySegment comparFunc
    = linker.upcallStub(comparHandle,
                        /* A Java description of a C function
                           implemented by a Java method! */
                        FunctionDescriptor.of(JAVA_INT,
                                              ADDRESS.withTargetLayout(JAVA_INT),
                                              ADDRESS.withTargetLayout(JAVA_INT)),
                        Arena.ofAuto());
```

We finally have a memory segment, `comparFunc`, which points to a stub that can be used to invoke our Java comparator function, and so we now have all we need to invoke the `qsort` downcall handle:

```
try (Arena arena = Arena.ofConfined()) {
    MemorySegment array
        = arena.allocateFrom(ValueLayout.JAVA_INT,
                             0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
    qsort.invoke(array, 10L, ValueLayout.JAVA_INT.byteSize(), comparFunc);
    int[] sorted = array.toArray(JAVA_INT);    // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
}
```

This code creates an off-heap array, copies the contents of a Java array into it, and then passes the array to the `qsort` handle along with the comparator function we obtained from the native linker. After the invocation, the contents of the off-heap array will be sorted according to our comparator function, written as Java code. We then extract a new Java array from the segment, which contains the sorted elements.

### Memory segments and byte buffers

The [`java.nio.channels`] API provides extensive functionality for performing I/O on files and sockets. In this API, I/O operations are expressed in terms of [`ByteBuffer`] objects rather than simple byte arrays. A client that writes data to a channel must first place it into a byte buffer; after reading data from a channel, the client must extract it from a byte buffer. For example, the following code uses a [`FileChannel`] to read the contents of a file into an off-heap byte buffer, 1024 bytes at a time:

```
try (FileChannel channel = FileChannel.open(... a file path ...)) {
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    int bytesRead;
    while ((bytesRead = channel.read(buffer)) != -1) {
        ... extract and process buffer contents ...
        buffer.clear();
    }
}
```

Since the byte buffer is likely to be smaller than the file, the code must repeatedly read from the channel and then clear the byte buffer in order to prepare for the next read operation.

Given this backdrop of low level buffer allocation and handling, it often [surprises developers](https://bugs.openjdk.org/browse/JDK-4724038) to learn that they cannot control the deallocation of off-heap byte buffers; they must, rather, wait for the garbage collector to reclaim them. If prompt deallocation is absolutely required then they can only resort to non-standard, non-deterministic techniques such as calling `sun.misc.Unsafe::invokeCleaner`.

The FFM API enables developers to combine the functionality of channels with the standard, deterministic deallocation offered by memory segments and arenas.

The FFM API includes a [`MemorySegment::asByteBuffer`] method that allows any memory segment be used as a byte buffer. The lifetime of the resulting byte buffer is determined by the temporal bounds of the memory segment, which in turn are set by the arena that was used to allocate the memory segment. Clients continue to read and write channels using byte buffers, but now have control over when the byte buffer’s memory is deallocated. Here is the previous example, revised to use a byte buffer whose memory is deallocated when the `try`-with-resources block closes the arena:

```
// try-with-resources manages two resources: a channel and an arena
try (FileChannel channel = FileChannel.open(... a file path ...);
     Arena offHeap       = Arena.ofConfined()) {
    ByteBuffer buffer = offHeap.allocate(1024).asByteBuffer();
    int readBytes;
    while ((readBytes = channel.read(buffer)) != -1) {
        ... unpack and process the contents of buffer ...
        buffer.clear();
    }
} // buffer’s memory is deallocated here
```

The FFM API also includes a [`MemorySegment::ofBuffer`] method that allows any byte buffer to be used as a memory segment, by creating a memory segment backed by the same region of memory. For example, the following method calls the native [`strlen`](#strlen) function, which takes a `char *`, with a memory segment produced from an off-heap byte buffer:

```
void readString(ByteBuffer offheapString) {
    MethodHandle strlen = ...
    long len = strlen.invokeExact(MemorySegment.ofBuffer(offheapString));
    ...
}
```

Byte buffers are found in many Java programs because they have long been the only supported way to pass off-heap data to native code. However, it is cumbersome for native code to access the data in a byte buffer because the native code must first call a [JNI function](https://docs.oracle.com/en/java/javase/21/docs/specs/jni/functions.html#getdirectbufferaddress) to get a pointer to the region of memory which backs the byte buffer. In contrast, the data in a memory segment is easy for native code to access, because when Java code passes a `MemorySegment` object to native code, the FFM API passes the base address of the memory segment — a pointer to its data — rather than the address of the `MemorySegment` object itself.

### Safety

Most of the FFM API is safe by design. Many scenarios that, in the past, required the use of JNI and native code can now be addressed by calling methods in the FFM API that never compromise the [integrity of the Java Platform][integrity]. For example, a significant use case for JNI — flexible memory allocation and deallocation — is now supported in Java code by memory segments and arenas, and requires no native code.

Part of the FFM API, however, is inherently unsafe. Java code can, for example, request a downcall method handle from the `Linker` but specify parameter types that are incompatible with those of the underlying foreign function. Invoking the resulting method handle will produce the same kind of failure — a VM crash, or undefined behavior by native code — that can occur when invoking a `native` method in JNI. Such failures cannot be prevented by the Java runtime, nor be caught by Java code. The FFM API can also be used to produce unsafe segments, that is, memory segments whose spatial and temporal bounds are user-provided and cannot be verified by the Java runtime (see [`MemorySegment::reinterpret`]).

In other words, any interaction between Java code and native code can compromise the integrity of the Java Platform. Accordingly, the unsafe methods in the FFM API are *restricted*. This means that their use is permitted, but by default causes a warning to be issued at run time. For example:

```
WARNING: A restricted method in java.lang.foreign.Linker has been called
WARNING: Linker::downcallHandle has been called by com.foo.Server in an unnamed module
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
```

Such warnings, which are written to the standard error stream, are issued at most once for each module whose code calls a restricted method.

To allow code in a module `M` to use unsafe methods without warnings, specify the `--enable-native-access=M` option on the `java` launcher command line. Specify multiple modules with a comma-separated list; specify `ALL-UNNAMED` to enable warning-free use by all code on the class path. In addition, the JAR-file manifest attribute `Enable-Native-Access: ALL-UNNAMED` can be used in an executable JAR to enable warning-free use by all code on the class path; no other module name can be given as the value of the attribute.

When the `--enable-native-access` option is present, any use of unsafe methods from outside the list of specified modules causes an `IllegalCallerException` to be thrown, rather than a warning to be issued. In a future release, it is likely that this option will be required in order to use unsafe methods; that is, if the option is not present then using unsafe methods will result not in a warning but rather an `IllegalCallerException`.

To ensure a consistent approach to how Java code interacts with native code, a related JEP [proposes to restrict the use of JNI](https://openjdk.org/jeps/8307341) in a similar way. It will still be possible to call `native` methods from Java code, and for native code to call unsafe JNI functions, but the `--enable-native-access` option will be required in order to avoid warnings and, later, exceptions. This aligns with the broader roadmap of [making the Java Platform safe out-of-the-box][integrity], requiring end users or application developers to opt-in to unsafe activities such as breaking strong encapsulation or linking to unknown code.


## Risks and Assumptions

Creating an API to access foreign memory in a way that is both safe and efficient is a daunting task. Since spatial and temporal bounds need to be checked upon every access, it is crucial that JIT compilers be able to optimize away these checks by, e.g., hoisting them outside of hot loops. The JIT implementations will likely require some work to ensure that uses of the API are as efficient and optimizable as uses of existing APIs such as `ByteBuffer` and `Unsafe`. The JIT implementations will also require work to ensure that uses of the native method handles produced by the API are at least as efficient and optimizable as uses of existing JNI native methods.


## Dependencies

The [jextract tool](https://github.com/openjdk/jextract) depends on the FFM API. It takes the header files for a native library and mechanically generates the downcall method handles required to interoperate with that library. This reduces the overhead of using native libraries from Java code.


[`Arena`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/Arena.html
[`FunctionDescriptor`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/FunctionDescriptor.html
[`StructLayout`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/StructLayout.html
[`Linker`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/Linker.html
[`MemoryLayout`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/MemoryLayout.html
[`MemorySegment`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/MemorySegment.html
[`SegmentAllocator`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/SegmentAllocator.html
[`SymbolLookup`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/SymbolLookup.html
[`ValueLayout`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/ValueLayout.html
[`VarHandle`]: https://docs.oracle.com/en/java/javase/21/docs//api/java.base/java/lang/invoke/VarHandle.html
[`MethodHandle`]: https://docs.oracle.com/en/java/javase/21/docs//api/java.base/java/lang/invoke/MethodHandle.html
[`java.lang.foreign`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/package-summary.html
[`libffi`]: https://github.com/libffi/libffi
[`Arena::global`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/Arena.html#global()
[`Arena::ofAuto`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/Arena.html#ofAuto()
[`Arena::ofConfined`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/Arena.html#ofConfined()
[`Arena::ofShared`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/Arena.html#ofShared()
[`SegmentAllocator::slicingAllocator`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/SegmentAllocator.html#slicingAllocator(java.lang.foreign.MemorySegment)
[`MemorySegment::reinterpret`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/MemorySegment.html#reinterpret(long,java.lang.foreign.Arena,java.util.function.Consumer)
[`MemorySegment::asByteBuffer`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/MemorySegment.html#asByteBuffer()
[`MemorySegment::ofBuffer`]: https://cr.openjdk.org/~mcimadamore/jdk/FFM_22_PR/javadoc/java.base/java/lang/foreign/MemorySegment.html#ofBuffer(java.nio.Buffer)
[`java.nio.channels`]: https://docs.oracle.com/en/java/javase/21/docs//api/java.base/java/nio/channels/package-summary.html
[`ByteBuffer`]: https://docs.oracle.com/en/java/javase/21/docs//api/java.base/java/nio/ByteBuffer.html
[`FileChannel`]: https://docs.oracle.com/en/java/javase/21/docs//api/java.base/java/nio/channels/FileChannel.html
[integrity]: https://openjdk.org/jeps/8305968