/* * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang.foreign; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.ref.CleanerFactory; import java.lang.foreign.MemorySegment.Scope; import java.util.function.Consumer; /** * An arena controls the lifecycle of native memory segments, providing both flexible * allocation and timely deallocation. *

* An arena has a {@linkplain MemorySegment.Scope scope} - the arena scope. * All the segments allocated by the arena are associated with the arena scope. As such, * the arena determines the temporal bounds of all the memory segments allocated by it. *

* Moreover, an arena also determines whether access to memory segments allocated by it * should be {@linkplain MemorySegment#isAccessibleBy(Thread) restricted} to specific * threads. An arena is a {@link SegmentAllocator} and features several allocation * methods that can be used by clients to obtain native segments. *

* The simplest arena is the {@linkplain Arena#global() global arena}. The global arena * features an unbounded lifetime. The scope of the global arena is the global * scope. As such, native segments allocated with the global arena are always accessible * and their backing regions of memory are never deallocated. Moreover, memory segments * allocated with the global arena can be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} * from any thread. * {@snippet lang = java: * MemorySegment segment = Arena.global().allocate(100, 1); // @highlight regex='global()' * ... * // segment is never deallocated! *} *

* Alternatively, clients can obtain an {@linkplain Arena#ofAuto() automatic arena}, that * is an arena which features a bounded lifetime that is managed, automatically, * by the garbage collector. The scope of an automatic arena is an automatic scope. As * such, the regions of memory backing memory segments allocated with the automatic arena * are deallocated at some unspecified time after the automatic arena (and all * the segments allocated by it) becomes {@linkplain java.lang.ref##reachability unreachable}, * as shown below: * {@snippet lang = java: * MemorySegment segment = Arena.ofAuto().allocate(100, 1); // @highlight regex='ofAuto()' * ... * segment = null; // the segment region becomes available for deallocation after this point *} * Memory segments allocated with an automatic arena can also be * {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} from any thread. *

* Rather than leaving deallocation in the hands of the Java runtime, clients will often * wish to exercise control over the timing of deallocation for regions of memory that * back memory segments. Two kinds of arenas support this, namely {@linkplain #ofConfined() confined} * and {@linkplain #ofShared() shared} arenas. They both feature bounded lifetimes that * are managed manually. For instance, when a confined arena is {@linkplain #close() closed} * successfully, its scope is {@linkplain Scope#isAlive() invalidated}. As a result, all * the memory segments allocated by the arena can no longer be accessed, and their * regions of memory are deallocated: * * {@snippet lang = java: * MemorySegment segment = null; * try (Arena arena = Arena.ofConfined()) { // @highlight regex='ofConfined()' * segment = arena.allocate(100); * ... * } // segment region deallocated here * segment.get(ValueLayout.JAVA_BYTE, 0); // throws IllegalStateException *} * * Memory segments allocated with a {@linkplain #ofConfined() confined arena} can only be * accessed (and closed) by the thread that created the arena. If access to a memory * segment from multiple threads is required, clients can allocate segments in a * {@linkplain #ofShared() shared arena} instead. *

* The characteristics of the various arenas are summarized in the following table: * *

* * * * * * * * * * * * * * * * * * * * * * * * * * * *
Arenas characteristics
KindBounded lifetimeExplicitly closeableAccessible from multiple threads
GlobalNoNoYes
AutomaticYesNoYes
ConfinedYesYesNo
SharedYesYesYes
* *

Safety and thread-confinement

* * Arenas provide strong temporal safety guarantees: a memory segment allocated by an * arena cannot be accessed after the arena has been closed. The cost of * providing this guarantee varies based on the number of threads that have access to the * memory segments allocated by the arena. For instance, if an arena is always created * and closed by one thread, and the memory segments allocated by the arena are always * accessed by that same thread, then ensuring correctness is trivial. *

* Conversely, if an arena allocates segments that can be accessed by multiple threads, * or if the arena can be closed by a thread other than the accessing thread, then * ensuring correctness is much more complex. For example, a segment allocated with the * arena might be accessed while another thread attempts, concurrently, to close * the arena. To provide the strong temporal safety guarantee without forcing every * client, even simple ones, to incur a performance impact, arenas are divided into * thread-confined arenas, and shared arenas. *

* Confined arenas, support strong thread-confinement guarantees. Upon creation, they are * assigned an owner thread, typically the thread which initiated the creation * operation. The segments created by a confined arena can only be * {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by the owner thread. * Moreover, any attempt to close the confined arena from a thread other than the owner * thread will fail with a {@link WrongThreadException}. *

* Shared arenas, on the other hand, have no owner thread. The segments created by a * shared arena can be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by * any thread. This might be useful when multiple threads need to access the same memory * segment concurrently (e.g. in the case of parallel processing). Moreover, a shared * arena can be closed by any thread. * *

Custom arenas

* * Clients can define custom arenas to implement more efficient allocation strategies, * or to have better control over when (and by whom) an arena can be closed. As an * example, the following code defines a slicing arena that behaves like a * confined arena (i.e., single-threaded access), but internally uses a * {@linkplain SegmentAllocator#slicingAllocator(MemorySegment) slicing allocator} to * respond to allocation requests. * When the slicing arena is closed, the underlying confined arena is also closed; this * will invalidate all segments allocated with the slicing arena (since the scope of the * slicing arena is the same as that of the underlying confined arena): * * {@snippet lang = java: * class SlicingArena implements Arena { * final Arena arena = Arena.ofConfined(); * final SegmentAllocator slicingAllocator; * * SlicingArena(long size) { * slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size)); * } * * public MemorySegment allocate(long byteSize, long byteAlignment) { * return slicingAllocator.allocate(byteSize, byteAlignment); * } * * public MemorySegment.Scope scope() { * return arena.scope(); * } * * public void close() { * arena.close(); * } * * } * } * * In other words, a slicing arena provides a vastly more efficient and scalable * allocation strategy, while still retaining the timely deallocation guarantee provided * by the underlying confined arena: * * {@snippet lang = java: * try (Arena slicingArena = new SlicingArena(1000)) { * for (int i = 0; i < 10; i++) { * MemorySegment s = slicingArena.allocateFrom(JAVA_INT, 1, 2, 3, 4, 5); * ... * } * } // all memory allocated is released here * } * * @implSpec * Implementations of this interface are thread-safe. * * @see MemorySegment * * @since 22 */ public interface Arena extends SegmentAllocator, AutoCloseable { /** * Creates a new arena that is managed, automatically, by the garbage collector. * Segments allocated with the returned arena can be * {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by any thread. * Calling {@link #close()} on the returned arena will result in an {@link UnsupportedOperationException}. *

* Memory segments {@linkplain #allocate(long, long) allocated} by the returned arena * are zero-initialized. * * @return a new arena that is managed, automatically, by the garbage collector */ static Arena ofAuto() { return MemorySessionImpl.createImplicit(CleanerFactory.cleaner()).asArena(); } /** * {@return the global arena} Segments allocated with the global arena can be * {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by any thread. * Calling {@link #close()} on the returned arena will result in * an {@link UnsupportedOperationException}. *

* Memory segments {@linkplain #allocate(long, long) allocated} by the returned arena * are zero-initialized. */ static Arena global() { class Holder { static final Arena GLOBAL = MemorySessionImpl.GLOBAL_SESSION.asArena(); } return Holder.GLOBAL; } /** * {@return a new confined arena} Segments allocated with the confined arena can * only be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by the * thread that created the arena, the arena's owner thread. *

* Memory segments {@linkplain #allocate(long, long) allocated} by the returned arena * are zero-initialized. */ static Arena ofConfined() { return MemorySessionImpl.createConfined(Thread.currentThread()).asArena(); } /** * {@return a new shared arena} Segments allocated with the shared arena can be * {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by any thread. *

* Memory segments {@linkplain #allocate(long, long) allocated} by the returned arena * are zero-initialized. */ static Arena ofShared() { return MemorySessionImpl.createShared().asArena(); } /** * Returns a native memory segment with the given size (in bytes) and alignment * constraint (in bytes). * The returned segment is associated with this {@linkplain #scope() arena scope}. * The segment's {@link MemorySegment#address() address} is the starting address of * the allocated off-heap region of memory backing the segment, and the address is * aligned according the provided alignment constraint. * * @implSpec * Implementations of this method must return a native segment featuring the * requested size, and that is compatible with the provided alignment constraint. * Furthermore, for any two segments {@code S1, S2} returned by this method, the * following invariant must hold: * * {@snippet lang = java: * S1.asOverlappingSlice(S2).isEmpty() == true * } * * @param byteSize the size (in bytes) of the off-heap region of memory backing * the native memory segment * @param byteAlignment the alignment constraint (in bytes) of the off-heap region * of memory backing the native memory segment * @return a new native memory segment * @throws IllegalArgumentException if {@code bytesSize < 0}, {@code byteAlignment <= 0}, * or if {@code byteAlignment} is not a power of 2 * @throws IllegalStateException if this arena has already been {@linkplain #close() closed} * @throws WrongThreadException if this arena is confined, and this method is called * from a thread other than the arena's owner thread */ @Override MemorySegment allocate(long byteSize, long byteAlignment); /** * {@return the arena scope} */ Scope scope(); /** * Closes this arena. If this method completes normally, the arena scope is no longer * {@linkplain Scope#isAlive() alive}, and all the memory segments associated with it * can no longer be accessed. Furthermore, any off-heap region of memory backing the * segments obtained from this arena are also released. * * @apiNote This operation is not idempotent; that is, closing an already closed arena * always results in an exception being thrown. This reflects a * deliberate design choice: failure to close an arena might reveal a bug * in the underlying application logic. * * @implSpec If this method completes normally, then * {@code this.scope().isAlive() == false}. * Implementations are allowed to throw {@link UnsupportedOperationException} * if an explicit close operation is not supported. * * @see Scope#isAlive() * * @throws IllegalStateException if the arena has already been closed * @throws IllegalStateException if a segment associated with this arena is being * accessed concurrently, e.g. by a * {@linkplain Linker#downcallHandle(FunctionDescriptor, Linker.Option...) downcall method handle} * @throws WrongThreadException if this arena is confined, and this method is called * from a thread other than the arena's owner thread * @throws UnsupportedOperationException if this arena cannot be closed explicitly * @throws RuntimeException if an exception is thrown while executing a custom cleanup action * associated with this arena (e.g. as a result of calling * {@link MemorySegment#reinterpret(long, Arena, Consumer)} or * {@link MemorySegment#reinterpret(Arena, Consumer)}). */ @Override void close(); }