Summary
This CSR refers to the latest iteration of the Foreign Memory Access and Foreign Linker API originally targeted for Java 14 and Java 16, respectively, with the goal of refining some of the API rough edges, as well as addressing the feedback received so far from developers.
Problem
Real-world use of the Foreign Memory Access and Foreign Linker APIs revealed some remaining usability issues, listed below:
Managing the life-cycle of memory segments is not intuitive, and can lead developers to write code that performs less efficiently than expected. For instance, the MemorySegment API heavily relies on dynamic ownership changes (see
MemorySegment::handoff
), where the ownership of a segment is updated in place, by killing the old segment and return a new one with new ownership characteristics. While for confined segment such an operation can be supported at a relatively low cost, the same cannot be said for shared segment (introduced in Java 16); since shared segment feature a relatively expensiveclose
operation, it is generally not feasible to dynamically change ownership of a shared segment in critical code paths.Shared segments makes it hard to support asynchronous IO operations (e.g.
SocketChannel::read(ByteBuffer)
) on buffer views obtained by such segments; this is due to the fact that a shared segment can be closed by any thread - possibly in the middle of an async IO operation. The API did not provide any workaround for this use case; for this reason, asynchronous operations on byte buffer views obtained from shared segments are not supported (as stated in the javadoc forMemorySegment::asByteBuffer
), and, as a result, clients are faced with a choice between shared segment and full byte buffer interoperability.When linking a foreign function, the address of said function has to be specified ahead of time (e.g. when the native method handle is obtained); while this is good in most cases when the user wants to call a foreign functions, some use cases require more flexibility. Certain libraries in fact, provide extension points by means of function pointers, with a well-defined signature. In such cases, it would be preferable to obtain a native method handle without having to specify an address, and late-bind the address when the foreign call is made.
Allocating native memory can often be the source of performance bottlenecks in real world applications; while the API allows a more optimized allocation, using the
NativeScope
abstraction, not all the API points which perform allocation can be provided the desired allocation strategy. For instance, it is not possible to pass aNativeScope
to a native function which returns a struct by value. In addition,NativeScope
only represent a possible choice in the allocator design space: the allocation scheme it provides is, essentially, a thread-confined arena-based allocation. It is not possible to makeNativeScope
more general and/or to support different allocation schemes.Some of the static factory methods in the API are hostile to the use of static imports - for instance, consider
MemoryLayout.ofSequence
.Both the Foreign Memory Access API and the Foreign Linker API contained unsafe API points, called restricted methods; access to restricted methods is controlled by a read-only JDK property (namely,
foreign.restricted
), whose value must be explicitly set topermit
for access to occur. While this solution is good enough to make restricted methods disabled by default, the use of a JDK property as a means to do so is rather ad-hoc and brittle.
Solution
Here we describe the main ideas behind the API changes brought forward in this CSR:
The new API models segment life-cycle with an explicit abstraction, named
ResourceScope
. AResourceScope
is a stateful abstraction which can be either alive, or closed. Resources managed by a resource scope can only be accessed if the owning resource scope is alive. Resource scopes can be confined (e.g. only thread which created the scope has access to the managed resources) or shared, and can be (optionally) associated with aCleaner
object, which is responsible for closing the scope in case the scope becomes unreachable. By making theResourceScope
abstraction a first-class citizen in the API, the semantics of other abstractions such asMemorySegment
,MemoryAddress
,VaList
becomes clearer: these abstractions cannot, in fact, be closed in isolation - they can instead be closed, by closing the resource scope they belong to. This allowed us to greatly simplify theMemorySegment
abstraction, and remove all API points related to dynamic ownership changes. Also,MemorySegment
is no longerAutoCloseable
(butResourceScope
is), which further clarifies the semantics of the API when multiple segments (e.g. slices) are owned by the same scope.As we have seen, there are cases where APIs might want to temporarily inhibit deterministic deallocation. The API now allows clients to acquire a
ResourceScope
; this returns aResourceScope.Handle
instance. This idiom can be used to perform operations on a segment (or any other resource managed by the scope) while the scope handle is being held by the client. Any attempt to close the resource scope when one or more handles have been acquired will result in an exception. When a client no longer need to work on a resource associated with the acquire scope, the acquire handle can be released. This allows to address interoperability issues between memory segments and byte buffer views in the context of asynchronous IO operations.The
CLinker
interface now offers an overload ofdowncallHandle
which accept noAddressable
parameter; the native method handle returned by this overload instead accepts an additional prefix argument (of type Addressable) which can be used to specify the foreign function entry point at call-time, rather than at link-time.A new abstraction, namely
SegmentAllocator
, replacesNativeScope
.SegmentAllocator
is a functional interface which provides several default methods which help when allocating off-heap memory from Java objects (e.g. on heap arrays). TheSegmentAllocator
interface provides some ready-made allocators, such as an arena allocator, which provides the same optimized allocation scheme previously provided byNativeScope
. Since nowSegmentAllocator
andResourceScope
are independent entities, clients can combine them at will, meaning that it is now possible. for instance, to create an arena allocator out of a shared resource scope. Several API points inCLinker
have been enhanced to take an extraSegmentAllocator
parameter, instead of theNativeScope
parameter accepted previously. Most notably, all native method handles generated byCLinker::downcallHandle
will now accept an additionalSegmentAllocator
parameter, in case the foreign function returns aMemorySegment
. (An additional overload ofCLinker::downcallHandle
allows the user to select an allocator at link-time, if desired). To facilitate conversion fromResourceScope
toSegmentAllocator
, some API points inCLinker
(e.g.CLinker::toCString
) define overloads accepting aResourceScope
parameter; this scope is then internally converted into a so called scoped allocator (an allocator which allocates segment tied to the provided scope). This makes the API simpler to use in case clients do not require an alternate, more efficient allocation strategy.Some of the static factory methods in the API were renamed to be more friendly with respect to static imports. All factories in the layout API have been renamed - e.g. from
ofXYZ
toXYZlayout
. In the newly added APIs (ResourceScope
andSegmentAllocator
) we used thenew
prefix whenever allocating a new instance was an important part of the API contract (this is especially true for resource scope creation). We also used theof
prefix to denote a loose conversion - e.g.SegmentAllocator.ofScope
is used to create an allocator from a resource scope.To allow access to restricted methods in the API, a new experimental command line option in the Java launcher is added, namely
--enable-native-access=<module list>
. This options accepts a list of modules (separated by commas), where a module name can also beALL-UNNAMED
(for the unnamed module). Adding this command line flag to the launcher has the effect of allowing access to restricted methods to a given set of modules (the list of modules specified in the command line option). Access to restricted methods from any other module not in the list is disallowed and will result in anIllegalCallerException
.
Specification
A specdiff of the changes as of April 29th, 2021 has been attached to this CSR (v2).
A link of the latest javadoc (as of April 29th, 2021) is included below:
A link of the latest specdiff (as of April 29th, 2021) is included below:
http://cr.openjdk.java.net/~mcimadamore/JEP-412/v2/specdiff/overview-summary.html
- csr of
-
JDK-8264774 Implementation of Foreign Function and Memory API (Incubator)
- Resolved
- relates to
-
JDK-8265033 JEP 412: Foreign Function & Memory API (Incubator)
- Closed