Foreign Functions That Return Pointers

Sometimes foreign functions allocate a region of memory, then return a pointer to that region. For example, the C standard library function void *malloc(size_t) allocates the requested amount of memory, in bytes, and returns a pointer to it. However, when you invoke a native function that returns a pointer, like malloc, the Java runtime has no insight into the size or the lifetime of the memory segment the pointer points to. Consequently, the FFM API uses a zero-length memory segment to represent this kind of pointer.

The following example invokes the C standard library function malloc. It prints a diagnostic message immediately after, which demonstrates that the pointer returned by malloc is a zero-length memory segment.

    static MemorySegment allocateMemory(long byteSize, Arena arena) throws Throwable {
        
        // Obtain an instance of the native linker
        Linker linker = Linker.nativeLinker();
        
        // Locate the address of malloc()
        var malloc_addr = linker.defaultLookup().find("malloc").orElseThrow();
        
        // Create a downcall handle for malloc()
        MethodHandle malloc = linker.downcallHandle(
            malloc_addr,
            FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
        );
        
        // Invoke malloc(), which returns a pointer
        MemorySegment segment = (MemorySegment) malloc.invokeExact(byteSize); 
        
        // The size of the memory segment created by malloc() is zero bytes!
        System.out.println(
            "Size, in bytes, of memory segment created by calling malloc.invokeExact(" +
            byteSize + "): " + segment.byteSize());
            
        // Localte the address of free()
        var free_addr = linker.defaultLookup().find("free").orElseThrow();
        
        // Create a downcall handle for free()
        MethodHandle free = linker.downcallHandle(
            free_addr,
            FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)
        );        
        
        // This reintepret method:
        // 1. Resizes the memory segment so that it's equal to byteSize
        // 2. Associates it with an existing arena
        // 3. Invokes free() to deallocate the memory allocated by malloc()
        //    when its arena is closed
        
        Consumer<MemorySegment> cleanup = s -> {
            try {
                free.invokeExact(s);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        };
            
        return segment.reinterpret(byteSize, arena, cleanup);
    }

The example prints a message similar to the following:

Size, in bytes, of memory segment created by calling malloc.invokeExact(100): 0

The FFM API uses zero-length memory segments to represent the following:

  • Pointers returned from a foreign function
  • Pointers passed by a foreign function to an upcall
  • Pointers read from a memory segment

If you try to access a zero-length memory segment, the Java runtime will throw an IndexOutOfBoundsException because the Java runtime can't safely access or validate any access operation of a region of memory whose size is unknown. In addition, zero-length memory segments are associated with a fresh scope that's always alive. Consequently, even though you can't directly access zero-length memory segments, you can pass them to other pointer-accepting foreign functions.

However, the MemorySegment::reinterpret method enables you to work with zero length memory segments so that you can safely access them and attach them to an existing arena so that the lifetime of the region of memory backing the segment can be managed automatically. This method takes three arguments:

  • The number of bytes to resize the memory segment: The example resizes it to the value of the parameter byteSize.
  • The arena with which to associate the memory segment: The example associates it to the arena specified by the parameter arena.
  • The action to perform when the arena is closed: The example deallocates the memory allocated by malloc by invoking the C standard library function void free(void *ptr), which deallocates the memory referenced by a pointer returned by malloc. Note that this is an example of passing a pointer pointing to a zero-length memory segment to a foreign function.

Note:

MemorySegment::reinterpret is a restricted method, which, if used incorrectly, might crash the JVM or silently result in memory corruption. See Restricted Methods for more information.

The following example calls allocateMemory(long, Arena) to allocate a Java string with malloc:

        String s = "My string!";
        try (Arena arena = Arena.ofConfined()) {
            
            // Allocate off-heap memory with malloc()
            var nativeText = allocateMemory(
                ValueLayout.JAVA_CHAR.byteSize() * (s.length() + 1), arena);
            
            // Access off-heap memory
            for (int i = 0; i < s.length(); i++ ) {
                nativeText.setAtIndex(ValueLayout.JAVA_CHAR, i, s.charAt(i)); 
            }
            
            // Add the string terminator at the end
            nativeText.setAtIndex(
                 ValueLayout.JAVA_CHAR, s.length(), Character.MIN_VALUE);
                 
            // Print the string
            for (int i = 0; i < s.length(); i++ ) {
                System.out.print((char)nativeText.getAtIndex(ValueLayout.JAVA_CHAR, i)); 
            }       
            System.out.println();            
        } catch (Throwable t) {
            t.printStackTrace();
        }

See Zero-length memory segments in the java.lang.foreign.MemorySegment API specification and Functions returning pointers in the java.lang.foreign.Linker API specification for more information.