Understanding addresses (pointers) – Foreign (Function) Memory API

Java Certification Exams and ProgrammingExams of Java, Introducing PaddingLayout, Java Exams Understanding addresses (pointers) – Foreign (Function) Memory API
0 Comments

144. Understanding addresses (pointers)

A memory segment has a memory address (pointer) expressed as a long number. An off-heap memory segment has a physical address that points out the memory region which backs the segment (base address). Each memory layout stored in this segment has its own memory address as well. For instance, here is an example of querying the base address of a memory segment via the address() method (arena is an instance of Arena):

MemorySegment segment = arena
  .allocate(ValueLayout.JAVA_INT, 1000);
long addr = segment.address(); // 2620870760384

On the other hand, an on-heap memory segment has a non-physical stable virtualized address typically representing an offset within the memory region of that segment (the client sees a stable address while the Garbage Collector can reallocate the region of memory inside the heap). For instance, an on-heap segment created via one of the ofArray() factory methods has an address of zero.Next, let’s focus only on off-heap memory segments. Let’s consider the following three memory segments containing integer values (arena is an instance of Arena):

MemorySegment i1 = arena.allocate(ValueLayout.JAVA_INT, 1);
MemorySegment i2 = arena.allocate(ValueLayout.JAVA_INT, 3);
MemorySegment i3 = arena.allocate(ValueLayout.JAVA_INT, 2);

Each of these segments has a memory address. Next, let’s create a segment containing their addresses (like a segment of pointers). First, we allocate such a segment via ValueLayout.ADDRESS as follows:

MemorySegment addrs = arena
  .allocateArray(ValueLayout.ADDRESS, 3);

Since each address is a long value, the size of addrs is 24 bytes. We can use the set() method and the offsets 0, 8, and 16 to set the addresses of i1, i2, and i3, or we can use the setAtIndex() and refer to offsets as indexes 0, 1, and 2:

addrs.setAtIndex(ValueLayout.ADDRESS, 0, i1);
addrs.setAtIndex(ValueLayout.ADDRESS, 1, i2);
addrs.setAtIndex(ValueLayout.ADDRESS, 2, i3);

We can represent this in the following diagram:

Figure 7.10 – Storing i1, i2, and i3 addresses in an array of addresses

In other words, we set the address of i1 at offset 0 in addrs, the address of i2 at offset 8, and the address of i3 at offset 16. The addrs segment doesn’t hold the data of i1, i2, and i3. It is just a segment of pointers that points to the memory addresses of i1, i2, and i3.If we call get()/getAtIndex() we will get an address:

MemorySegment addr1 = addrs.getAtIndex(ValueLayout.ADDRESS,0);
MemorySegment addr2 = addrs.getAtIndex(ValueLayout.ADDRESS,1);
MemorySegment addr3 = addrs.getAtIndex(ValueLayout.ADDRESS,2);

We can represent this in the following diagram:

Figure 7.11 – Getting addresses from the array of addresses

But, check out the return type. Is not a long value! It is a MemorySegment. The returned native memory segments (addr1, addr2, addr3) are automatically associated with the global scope. They have the size 0 (limit: 0) and each of them wraps the returned address of the given offset/index (the long value is available via addr1/2/3.address()). However, in the case of an unbounded address layout (ValueLayout.ADDRESS.asUnbounded()), the size is expected to be Long.MAX_VALUE (9223372036854775807).This means that we shouldn’t do this:

addr1.get(ValueLayout.JAVA_INT, 0); DON’T DO THIS!

This causes an IndexOutOfBoundsException since addr1 has a size of 0 bytes. Getting the integer value associated with an address can be done via the ofAddress() method as follows:

int v1 = MemorySegment.ofAddress(
  addr1.address(), 4).get(ValueLayout.JAVA_INT, 0);

First, we call ofAddress() and we pass the addr1 address and the size of the segment. This will create a native memory segment with the given address and size. Next, we read the integer value stored at this address at offset 0. The same thing can be done for addr2 and addr3:

int v2 = MemorySegment.ofAddress(
  addr2.address(), 4).get(ValueLayout.JAVA_INT, 0);
int v3 = MemorySegment.ofAddress(
  addr3.address(), 4).get(ValueLayout.JAVA_INT, 0);

We can check if two long addresses are equal via the == operator:

addr1.address() == i1.address() // true

Or, via equals():

addr1.equals(i1) // true

At this moment, we have that i1=1, i2=3, and i3=2. Let’s manipulate only the addresses to obtain i1=1, i2=2, and i3=3. So we want to switch the integer values of i2 and i3 by switching the addresses, not the values. First, we store the i2 address as a long:

long i2Addr = i2.address();

Next, we set the i2 address as the i3 address:

i2 = MemorySegment.ofAddress(i3.address(), 4);

Finally, we set the address of i3 as the address of i2:

i3 = MemorySegment.ofAddress(i2Addr, 4);

Done! Now, i1=1, i2=2, and i3=3. I hope you found this exercise useful for understanding how to manipulate values, offsets, and memory addresses.


Leave a Reply

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