source, binary, behavioral
The introduction of the `ResourceScope` abstraction, which is now a _required_ parameter when creating resources managed by the API (e.g. memory segments) is source incompatible. On top of that, some of the abstractions (segments, valists) no longer implement the `AutoCloseable` interface, leading to more source compatibility issues. Finally, some of the renaming around static factory methods has also potential for breaking existing source code.The introduction of the `ResourceScope` abstraction, which is now a _required_ parameter when creating resources managed by the API (e.g. memory segments) is source incompatible. On top of that, some of the abstractions (segments, valists) no longer implement the `AutoCloseable` interface, leading to more source compatibility issues. Finally, some of the renaming around static factory methods has also potential for breaking existing source code.
Java API, add/remove/modify command line option
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.
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 expensive
closeoperation, 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 for
MemorySegment::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
NativeScopeabstraction, not all the API points which perform allocation can be provided the desired allocation strategy. For instance, it is not possible to pass a
NativeScopeto a native function which returns a struct by value. In addition,
NativeScopeonly 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 make
NativeScopemore 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
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 to
permitfor 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.
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
ResourceScopeis 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 a
Cleanerobject, which is responsible for closing the scope in case the scope becomes unreachable. By making the
ResourceScopeabstraction a first-class citizen in the API, the semantics of other abstractions such as
VaListbecomes 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 the
MemorySegmentabstraction, and remove all API points related to dynamic ownership changes. Also,
MemorySegmentis no longer
ResourceScopeis), 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 a
ResourceScope.Handleinstance. 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.
CLinkerinterface now offers an overload of
downcallHandlewhich accept no
Addressableparameter; 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
SegmentAllocatoris a functional interface which provides several default methods which help when allocating off-heap memory from Java objects (e.g. on heap arrays). The
SegmentAllocatorinterface provides some ready-made allocators, such as an arena allocator, which provides the same optimized allocation scheme previously provided by
NativeScope. Since now
ResourceScopeare 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 in
CLinkerhave been enhanced to take an extra
SegmentAllocatorparameter, instead of the
NativeScopeparameter accepted previously. Most notably, all native method handles generated by
CLinker::downcallHandlewill now accept an additional
SegmentAllocatorparameter, in case the foreign function returns a
MemorySegment. (An additional overload of
CLinker::downcallHandleallows the user to select an allocator at link-time, if desired). To facilitate conversion from
SegmentAllocator, some API points in
CLinker::toCString) define overloads accepting a
ResourceScopeparameter; 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
XYZlayout. In the newly added APIs (
SegmentAllocator) we used the
newprefix whenever allocating a new instance was an important part of the API contract (this is especially true for resource scope creation). We also used the
ofprefix to denote a loose conversion - e.g.
SegmentAllocator.ofScopeis 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 be
ALL-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 an
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:
- csr of
JDK-8264774 Implementation of Foreign Function and Memory API (Incubator)
- relates to
JDK-8265033 JEP 412: Foreign Function & Memory API (Incubator)