Memory Segments and Arenas
You can access off-heap or on-heap memory with the Foreign Function and Memory (FFM) API through the MemorySegment interface. Each memory segment is associated with, or backed by, a contiguous region of memory. There are two kinds of memory segments:
- Heap segment: This is a memory segment backed by a region of memory inside the Java heap, an on-heap region.
- Native segment: This is a memory segment backed by a region of memory outside the Java heap, an off-heap region. The examples in this chapter demonstrate how to allocate and access native segments.
An arena controls the lifecycle of native memory segments. To create an arena, use one of the methods in the Arena interface, such as Arena.ofConfined(). You use an arena to allocate a memory segment. Each arena has a scope, which specifies when the region of memory that backs the memory segment will be deallocated and is no longer valid. A memory segment can only be accessed if the scope associated with it is still valid or alive.
Most of the examples described in this chapter use a confined arena, which is created with Arena::ofConfined. A confined arena provides a bounded and deterministic lifetime. Its scope is alive from when it's created to when it's closed. A confined arena has an owner thread. This is typically the thread that created it. Only the owner thread can access the memory segments allocated in a confined arena. You'll get an exception if you try to close a confined arena with a thread other than the owner thread.
There are other kinds of arenas:
- A shared arena, which is created with Arena::ofShared, has no owner thread. Multiple threads may access the memory segments allocated in a shared arena. In addition, any thread may close a shared arena, and the closure is guaranteed to be safe and atomic. See Slicing Memory Segments for an example of a shared arena.
- An automatic arena, which is created with Arena::ofAuto. This is an area that's managed, automatically, by the garbage collector. Any thread can access memory segments allocated by an automatic arena. If you call Arena::close on an automatic arena, you'll get a UnsupportedOperationException.
- A global arena, which is created with Arena::global. Any thread can access memory segments allocated with this arena. In addition, the region of memory of these memory segments is never deallocated; if you call Arena::close on a global arena, you'll get a UnsupportedOperationException.
The following example allocates a memory segment with an arena, stores a Java
String in the off-heap memory associated with the memory segment,
and then prints the contents of the off-heap memory. At the end of the
try
-with-resources block, the arena is closed, and the off-heap
memory associated with the memory segment is deallocated.
String s = "My string";
try (Arena arena = Arena.ofConfined()) {
// Allocate off-heap memory
MemorySegment nativeText = arena.allocateFrom(s);
// Access off-heap memory
for (int i = 0; i < s.length(); i++ ) {
System.out.print((char)nativeText.get(ValueLayout.JAVA_BYTE, i));
}
} // Off-heap memory is deallocated
The following sections describe this example in detail:
Allocating a Memory Segment with an Arena and Storing a String in It
The Arena interface extends the SegmentAllocator interface, which contains methods that both allocate off-heap memory and copy Java data into it. The previous example calls the method SegmentAllocator.allocateFrom(String), which allocates a memory segment with an arena, converts a string into a UTF-8 encoded, null-terminated C string, and then stores the string into the memory segment.
String s = "My string";
try (Arena arena = Arena.ofConfined()) {
// Allocate off-heap memory
MemorySegment nativeText = arena.allocateFrom(s);
// ...
Tip:
You can call SegmentAllocator.allocateFrom(String, Charset) to store a string with a different charset. The SegmentAllocator interface contains several allocateFrom methods that enable you to store data of various data types in a memory segment.See Memory Layouts and Structured Access for information about allocating and accessing more complicated native data types such as C structures.
Printing the Contents of Off-Heap Memory
The following code prints the characters stored in the MemorySegment named nativeText
:
// Access off-heap memory
for (int i = 0; i < s.length(); i++ ) {
System.out.print((char)nativeText.get(ValueLayout.JAVA_BYTE, i));
}
The MemorySegment interface contains various access methods that enable you to read from or write to memory segments. Each access method takes as an argument a value layout, which models the memory layout associated with values of basic data types such as primitives. A value layout encodes the size, the endianness or byte order, the bit alignment of the piece of memory to be accessed, and the Java type to be used for the access operation.
For example, MemoryLayout.get(ValueLayout.OfByte,long) takes as an argument
ValueLayout.JAVA_BYTE
. This value layout has the following
characteristics:
- The same size as a Java
byte
- Byte alignment set to 1: This means that the memory layout is stored at a memory address that's a multiple of 8 bits.
- Byte order set to
ByteOrder.nativeOrder()
: A system can order the bytes of a multibyte value from most significant to least significant (big-endian) or from least significant to most significant (little-endian).
Closing an Arena
When an arena is closed, such as through a
try
-with-resources statement, then the arena's scope is no longer alive:
All memory segments associated with its scope are invalidated, and the memory regions
backing them are deallocated.
If you try to access a memory segment associated with an arena scope that's closed, you'll get an IllegalStateException, which the following example demonstrates:
String s = "My String";
MemorySegment nativeText;
try (Arena arena = Arena.ofConfined()) {
// Allocate off-heap memory
nativeText = arena.allocateFrom(s);
}
for (int i = 0; i < s.length(); i++ ) {
// Exception in thread "main" java.lang.IllegalStateException: Already closed
System.out.print((char)nativeText.get(ValueLayout.JAVA_BYTE, i));
}