Introducing Arena and MemorySegment – Foreign (Function) Memory API


142. Introducing Arena and MemorySegment

A MemorySegment shapes a heap or native memory segment. A heap segment accesses on-heap memory, while a native segment accesses the off-heap memory. In both cases, we talk about a contiguous region of memory that has a lifespan bounded by space and time.Among its characteristics, a memory segment has a size in bytes, an alignment of bytes, and a scope. The scope is represented via the java.lang.foreign.SegmentScope interface and determines the lifespan of the memory segment and we have:The global scope – The memory segments having the global scope are always accessible. In other words, the regions of memory allocated to these segments are never deallocated and their global scope remains alive forever. Here is an example of creating a native memory segment of 8 bytes in the global scope:

MemorySegment globalSegment = MemorySegment
  .allocateNative(8, SegmentScope.global());

The auto scope – The memory segments having the automatic scope are managed by the Garbage Collector. In other words, the Garbage Collector determines when the regions of memory backing these segments can be safely deallocated. Here is an example of creating a native memory segment of 8 bytes in the auto scope:

MemorySegment autoSegment = MemorySegment
  .allocateNative(8, SegmentScope.auto());

The arena scope – A strict control of the memory segment’s lifespan (allocation/deallocation and lifetime) can be obtained via an instance of java.lang.foreign.Arena. An Arena has a scope that typically leaves in a try-with-resources block. When the Arena is closed (by explicitly calling close(), or by simply leaving the arena’s try-with-resources block), its scope is closed and all memory segments associated with this scope are destroyed and memory is deallocated automatically.Opening an Arena can be done in two modes: confined or shared. A confined arena is opened via openConfined() and is owned by the current thread – the memory segments associated with the scope of a confined arena can only be accessed by the thread that created the arena. A shared arena is opened via openShared() and can be shared by multiple threads – the memory segments associated with the scope of the shared arena can be accessed by any thread (for instance, this can be useful for performing parallel computations on memory segments).In code lines, an Arena can be created as follows:

try (Arena arena = Arena.openConfined()) {
  // work with memory segments (MS1, MS2, …)
}
// here, memory segments MS1, MS2, …, have been deallocated

By calling arena.scope() we obtain the SegmentScope of the arena, and by calling arena.scope().isAlive() we can find out if the current scope is alive or not. A memory segment is accessible only if its scope is alive, so as long as the arena’s scope is alive. Here it is a memory segment of 8 bytes in arena scope:

try (Arena arena = Arena.openConfined()) {
  MemorySegment arenaSegment = MemorySegment
    .allocateNative(8, arena.scope());
}

Before going further, let’s briefly introduce the memory layouts.

Introducing memory layouts (ValueLayout)

Memory layouts are shaped by the java.lang.foreign.MemoryLayout interface and their goal is to describe the content of memory segments.We have simple memory layouts including ValueLayout and PaddingLayout, but we also have complex memory layouts for describing complex memory segments such as SequenceLayout, StructLayout, UnionLayout, and GroupLayout. The complex layouts are useful to model hierarchical user-defined data types such as C-like sequences, structures, unions, and so on.

Allocating memory segments of value layouts

For now, we are interested in ValueLayout. This is a simple memory layout that is useful to represent basic Java data types such as int, float, double, char, byte, and so on. In an API specific exprimation, a ValueLayout.JAVA_LONG is a layout whose carrier is long.class, a ValueLayout.JAVA_DOUBLE is a layout whose carrier is double.class, and so on. The carrier of a value layout can be obtained via the carrier() method.For instance, let’s assume that we need a memory segment for storing a single int value. We know that a Java int needs 4 bytes, so our segment can be allocated as follows:

MemorySegment segment = MemorySegment
  .allocateNative(4, arena.scope());

But, we can achieve the same thing via ValueLayout as follows:

MemorySegment segment = MemorySegment
  .allocateNative(ValueLayout.JAVA_INT, arena.scope());

Having an Arena instance (arena), we can allocate a memory segment directly via a set of allocate() methods. For instance, we can express the previous code via the allocate(long byteSize) and allocate(MemoryLayout layout) methods as follows:

MemorySegment segment = arena.allocate(4);
MemorySegment segment = arena
  .allocate(ValueLayout.JAVA_INT.byteSize());
MemorySegment segment = arena
  .allocate(ValueLayout.JAVA_INT);

Here is another example of allocating a memory segment for storing a Java double using the byte alignment specific to ValueLayout.JAVA_DOUBLE:

// using the ‘arena’ instance (it has the arena’s scope)
MemorySegment segment = arena.allocate(
  ValueLayout.JAVA_DOUBLE.byteSize(),
  ValueLayout.JAVA_DOUBLE.byteAlignment());
// using the allocateNative() method
MemorySegment segment = MemorySegment.allocateNative(
  ValueLayout.JAVA_DOUBLE.byteSize(),
  ValueLayout.JAVA_DOUBLE.byteAlignment(), arena.scope());

Or, allocating a memory segment for storing a Java char can be done as follows:

// using the ‘arena’ instance (it has the arena’s scope)
MemorySegment segment = arena.allocate(ValueLayout.JAVA_CHAR);
// using the allocateNative() method
MemorySegment segment = MemorySegment.allocateNative(
  ValueLayout.JAVA_CHAR.byteSize(), arena.scope());

Now, that we know how to allocate a memory segment to different data types let’s see how we can set/get some values.

Leave a Reply

Your email address will not be published. Required fields are marked *