Backing a Memory Segment with a Memory Region Inside a File

A memory region inside a file can back a memory segment. To do this, you map a region of a file channel into a memory segment with the FileChannel::map method.

The following example is similar to the one described in Memory Layouts and Structured Access. It creates a memory segment and then populates it with a native array of 10 Point structures. The difference is that the memory segment is mapped to a region in a file channel. The example creates a file named point-array.data, and then populates it. It then opens point-array.data with a read-only file channel and uses the FFM API to retrieve its contents.

public class MemoryMappingExample {
    
    static final SequenceLayout ptsLayout
        = MemoryLayout.sequenceLayout(10,
            MemoryLayout.structLayout(
                ValueLayout.JAVA_INT.withName("x"),
                ValueLayout.JAVA_INT.withName("y")));
            
    static final VarHandle xHandle
        = ptsLayout.varHandle(
            PathElement.sequenceElement(),
            PathElement.groupElement("x"));
            
    static final VarHandle yHandle
        = ptsLayout.varHandle(
            PathElement.sequenceElement(),
            PathElement.groupElement("y"));                        

    public static void main(String[] args) {
        MemoryMappingExample myApp = new MemoryMappingExample();
        try {
            Files.deleteIfExists(Paths.get("point-array.data"));
            myApp.createFile();
            myApp.readFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    void createFile() throws Exception {
        
        try (var fc = FileChannel.open(Path.of("point-array.data"),
                      Set.of(CREATE, READ, WRITE));
             Arena arena = Arena.ofConfined()) {
                 
            MemorySegment mapped = fc.map(READ_WRITE, 0L, ptsLayout.byteSize(), arena);
            
            System.out.println("Empty mapped segment:");
            System.out.println(toHex(mapped));

            for (int i = 0; i < ptsLayout.elementCount(); i++) {
                int xValue = i;
                int yValue = i * 10;
                xHandle.set(mapped, 0L, (long) i, xValue);
                yHandle.set(mapped, 0L, (long) i, yValue);
            }
            
            System.out.println("Populated mapped segment:");
            System.out.println(toString(mapped));
            System.out.println("Populated mapped segment in hex:");
            System.out.println(toHex(mapped));
        } 
    }
    
    
    void readFile() throws Exception {
        try (var fc = FileChannel.open(Path.of("point-array.data"),
                      Set.of(SPARSE, READ));
             Arena arena = Arena.ofConfined()) {
                 
            MemorySegment mapped = fc.map(READ_ONLY, 0L, ptsLayout.byteSize(), arena);
            
            System.out.println("Contents of point-array.data:");
            System.out.println(toString(mapped));
        }            
    }

    static String toString(MemorySegment seg) {
        String outputString = "";
            for (int i = 0; i < ptsLayout.elementCount(); i++) {
                int xVal = (int) xHandle.get(seg, 0L, (long) i);
                int yVal = (int) yHandle.get(seg, 0L, (long) i);
                outputString += "(" + xVal + ", " + yVal + ")";
                if ((i+1 != ptsLayout.elementCount())) outputString += "\n";
            }
        return outputString;
    }
    
    static String toHex(MemorySegment seg) {
        String outputString = "";
        HexFormat formatter = HexFormat.of();
        
        byte[] byteArray = seg.toArray(java.lang.foreign.ValueLayout.JAVA_BYTE);
                
        for (int i = 0; i < byteArray.length; i++) {
            outputString += formatter.toHexDigits(byteArray[i]) + " ";
            if ((i+1) % 8 == 0 && (i+1) % 16 != 0) {
                outputString += " ";
            }
            if ((i+1) % 16 == 0 && (i+1) < byteArray.length) {
                outputString += "\n";
            }    
        }
        return outputString;
    }    
}

Tip:

It is recommended that you declare VarHandles as static final for performance reasons.

It prints the following output:

Empty mapped segment:
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
Populated mapped segment:
(0, 0)
(1, 10)
(2, 20)
(3, 30)
(4, 40)
(5, 50)
(6, 60)
(7, 70)
(8, 80)
(9, 90)
Populated mapped segment in hex:
00 00 00 00 00 00 00 00  01 00 00 00 0a 00 00 00
02 00 00 00 14 00 00 00  03 00 00 00 1e 00 00 00
04 00 00 00 28 00 00 00  05 00 00 00 32 00 00 00
06 00 00 00 3c 00 00 00  07 00 00 00 46 00 00 00
08 00 00 00 50 00 00 00  09 00 00 00 5a 00 00 00
Contents of point-array.data:
(0, 0)
(1, 10)
(2, 20)
(3, 30)
(4, 40)
(5, 50)
(6, 60)
(7, 70)
(8, 80)
(9, 90)

You can verify the contents of point-array.data with another application, such as the UNIX command-line tool hexdump:

$ hexdump -C point-array.data
00000000  00 00 00 00 00 00 00 00  01 00 00 00 0a 00 00 00  |................|
00000010  02 00 00 00 14 00 00 00  03 00 00 00 1e 00 00 00  |................|
00000020  04 00 00 00 28 00 00 00  05 00 00 00 32 00 00 00  |....(.......2...|
00000030  06 00 00 00 3c 00 00 00  07 00 00 00 46 00 00 00  |....<.......F...|
00000040  08 00 00 00 50 00 00 00  09 00 00 00 5a 00 00 00  |....P.......Z...|
00000050

The following highlighted statement creates a FileChannel that's open for reading and writing:

try (var fc = FileChannel.open(Path.of("point-array.data"),
              Set.of(CREATE, READ, WRITE));
     Arena arena = Arena.ofConfined()) {

The statement specifies the following StandardOpenOption options:

  • CREATE: Creates a new file if it does not exist
  • READ: Opens the file for read access
  • WRITE: Opens the file for write access

The following statement creates a new memory segment and maps it a region in a file channel:

MemorySegment mapped = fc.map(READ_WRITE, 0L, ptsLayout.byteSize(), arena);

The statement calls the method FileChannel.map(FileChannel.MapMode, long, long, Arena) and creates a memory segment with the following characteristics:

  • Can both read from and write to the file point-array.data
  • Starts at the beginning of the file
  • Has the same size as the file
  • Its lifecycle is controlled by the previously declared confined arena

The example populates the mapped memory segment the same way as the example described in Memory Layouts and Structured Access.

To read the contents of point-array.data, the example creates a read-only file channel:

var fc = FileChannel.open(Path.of("point-array.data"),
         Set.of(READ)

The example then creates a read-only memory segment that's mapped to point-array.data:

MemorySegment mapped = fc.map(READ_ONLY, 0L, ptsLayout.byteSize(), arena);