Memory Layouts and Structured Access
Accessing structured data using only basic operations can lead to hard-to-read code that's difficult to maintain. Instead, you can use memory layouts to more efficiently initialize and access more complicated native data types such as C structures.
For example, consider the following C declaration, which defines an array of
Point
structures, where each Point
structure has
two members, Point.x
and Point.y
:
struct Point {
int x;
int y;
} pts[10];
You can initialize and access such a native array as follows:
try (Arena arena = Arena.ofConfined()) {
MemorySegment segment =
arena.allocate((long)(2 * 4 * 10), 1);
for (int i = 0; i < 10; i++) {
int xValue = i;
int yValue = i * 10;
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2), xValue);
segment.setAtIndex(ValueLayout.JAVA_INT, (i * 2) + 1, yValue);
}
for (int i = 0; i < 10; i++) {
int xVal = segment.getAtIndex(ValueLayout.JAVA_INT, (i * 2));
int yVal = segment.getAtIndex(ValueLayout.JAVA_INT, (i * 2) + 1);
System.out.println("(" + xVal + ", " + yVal + ")");
}
}
The first argument in the call to the Arena::allocate method calculates the number of bytes required for the
array. The arguments in the calls to the MemorySegment::setAtIndex method calculate which memory
address offsets to write into each member of a Point
structure. Similar
arguments perform the same calculations for the MemorySegment::getAtIndex method. To avoid these calculations,
you can use a memory layout.
To represent the array of Point
structures, the following
example uses a sequence memory layout:
try (Arena arena = Arena.ofConfined()) {
SequenceLayout ptsLayout
= MemoryLayout.sequenceLayout(10,
MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")));
VarHandle xHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("x"));
VarHandle yHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("y"));
MemorySegment segment = arena.allocate(ptsLayout);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xValue = i;
int yValue = i * 10;
xHandle.set(segment, 0L, (long) i, xValue);
yHandle.set(segment, 0L, (long) i, yValue);
}
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xVal = (int) xHandle.get(segment, 0L, (long) i);
int yVal = (int) yHandle.get(segment, 0L, (long) i);
System.out.println("(" + xVal + ", " + yVal + ")");
}
}
The first statement creates a sequence memory layout, which is represented
by a SequenceLayout object. It contains a sequence of ten structure
layouts, which are represented by StructLayout objects. The method MemoryLayout::structLayout returns a StructLayout object. Each structure layout contains two JAVA_INT value layouts named x
and
y
:
SequenceLayout ptsLayout
= MemoryLayout.sequenceLayout(10,
MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("x"),
ValueLayout.JAVA_INT.withName("y")));
The predefined value ValueLayout.JAVA_INT contains information about how many bytes a Java int value requires.
The next statements create two memory-access VarHandles that obtain memory address offsets. A VarHandle is a dynamically strongly typed reference to a variable or to a parametrically-defined family of variables, including static fields, non-static fields, array elements, or components of an off-heap data structure.
VarHandle xHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("x"));
VarHandle yHandle
= ptsLayout.varHandle(PathElement.sequenceElement(),
PathElement.groupElement("y"));
The method PathElement.sequenceElement() retrieves a memory layout from a
sequence layout. In this example, it retrieves one of the structure layouts from
ptsLayout
. The method call PathElement.groupElement("x") retrieves a memory layout named
x
. You can create a memory layout with a name with the withName(String) method.
The for
statements call VarHandle::set and VarHandle::get to access memory like MemorySegment::setAtIndex and MemorySegment::getAtIndex.
MemorySegment segment = arena.allocate(ptsLayout);
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xValue = i;
int yValue = i * 10;
xHandle.set(segment, 0L, (long) i, xValue);
yHandle.set(segment, 0L, (long) i, yValue);
}
for (int i = 0; i < ptsLayout.elementCount(); i++) {
int xVal = (int) xHandle.get(segment, 0L, (long) i);
int yVal = (int) yHandle.get(segment, 0L, (long) i);
System.out.println("(" + xVal + ", " + yVal + ")");
}
In this example, the set
method uses four arguments:
segment
: the memory segment in which to set the value0L
: the base offset, which is along
coordinate that points to the start of the array(long) i
: a secondlong
coordinate that indicates the array index in which to set the valuexValue
andyValue
: the actual value to set
The VarHandles xHandle
and yHandle
know the
size of the Point
structure (8 bytes) and the size of its
int
members (4 bytes). This means you don't have to calculate the
number of bytes required for the array's elements or the memory address offsets like in
the setAtIndex method.
Tip:
The base offset enables you to express complex access operations by injecting additional offset computation into the VarHandle. In particular, you can use memory segments and base offsets to model variable-length arrays. These are arrays whose size are not known statically and that cannot be represented using a sequence layout. You can access such memory segments with the MemoryLayout::arrayElementVarHandle method. See the section Working with variable-length arrays in the JavaDoc API documentation for the MemoryLayout interface for examples.