The Foreign Function & Memory API (FFM API) is a Java incubator API that enables Java programs to interoperate with code and data outside the Java runtime. It provides a pure-Java replacement for JNI (Java Native Interface) with better performance, safety, and ease of use.
Understanding FFM API Components
Key Features:
- Memory Access: Safe allocation and access to native memory
- Foreign Functions: Call native functions from Java code
- Type Safety: Memory access validation at runtime
- Resource Management: Automatic resource cleanup
- Vector Support: SIMD operations via Vector API integration
Project Setup and Dependencies
<properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- JUnit for testing --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <compilerArgs> <arg>--enable-preview</arg> <arg>--add-modules=jdk.incubator.foreign</arg> </compilerArgs> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> <configuration> <argLine>--enable-preview --add-modules=jdk.incubator.foreign</argLine> </configuration> </plugin> </plugins> </build>
Core Memory Management
package com.example.ffm; import jdk.incubator.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.file.Path; /** * Basic Memory Allocation and Management with FFM API */ public class MemoryManagement { /** * Demonstrates basic memory allocation and access */ public static void basicMemoryAllocation() { // Using Arena for automatic resource management try (Arena arena = Arena.ofConfined()) { // Allocate a segment of 100 bytes MemorySegment segment = arena.allocate(100); System.out.println("Allocated segment: " + segment.byteSize() + " bytes"); // Write data to the segment segment.set(ValueLayout.JAVA_BYTE, 0, (byte) 65); // 'A' segment.set(ValueLayout.JAVA_BYTE, 1, (byte) 66); // 'B' segment.set(ValueLayout.JAVA_BYTE, 2, (byte) 67); // 'C' // Read data back byte firstByte = segment.get(ValueLayout.JAVA_BYTE, 0); System.out.println("First byte: " + (char) firstByte); // Bulk operations byte[] data = {68, 69, 70}; // D, E, F MemorySegment.copy(data, 0, segment, ValueLayout.JAVA_BYTE, 3, 3); // Print the string byte[] result = new byte[6]; MemorySegment.copy(segment, ValueLayout.JAVA_BYTE, 0, result, 0, 6); System.out.println("String: " + new String(result)); } // Arena automatically closed here, memory freed } /** * Working with structured data (like C structs) */ public static void structuredMemoryLayout() { try (Arena arena = Arena.ofConfined()) { // Define a struct layout: { int id; double value; boolean flag; } GroupLayout structLayout = MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("id"), ValueLayout.JAVA_DOUBLE.withName("value"), ValueLayout.JAVA_BOOLEAN.withName("flag") ); // Allocate array of 5 structs SequenceLayout arrayLayout = MemoryLayout.sequenceLayout(5, structLayout); MemorySegment arraySegment = arena.allocate(arrayLayout); // Calculate offsets long idOffset = structLayout.byteOffset(MemoryLayout.PathElement.groupElement("id")); long valueOffset = structLayout.byteOffset(MemoryLayout.PathElement.groupElement("value")); long flagOffset = structLayout.byteOffset(MemoryLayout.PathElement.groupElement("flag")); long structSize = structLayout.byteSize(); // Initialize structs for (int i = 0; i < 5; i++) { long baseOffset = i * structSize; arraySegment.set(ValueLayout.JAVA_INT, baseOffset + idOffset, i * 10); arraySegment.set(ValueLayout.JAVA_DOUBLE, baseOffset + valueOffset, i * 1.5); arraySegment.set(ValueLayout.JAVA_BOOLEAN, baseOffset + flagOffset, i % 2 == 0); } // Read and print structs for (int i = 0; i < 5; i++) { long baseOffset = i * structSize; int id = arraySegment.get(ValueLayout.JAVA_INT, baseOffset + idOffset); double value = arraySegment.get(ValueLayout.JAVA_DOUBLE, baseOffset + valueOffset); boolean flag = arraySegment.get(ValueLayout.JAVA_BOOLEAN, baseOffset + flagOffset); System.out.printf("Struct[%d]: id=%d, value=%.2f, flag=%b%n", i, id, value, flag); } } } /** * Memory segment slicing and views */ public static void memorySlicing() { try (Arena arena = Arena.ofConfined()) { // Allocate a large segment MemorySegment original = arena.allocate(1024); // Fill with pattern for (int i = 0; i < 1024; i++) { original.set(ValueLayout.JAVA_BYTE, i, (byte) (i % 256)); } // Create slices (views) MemorySegment slice1 = original.asSlice(0, 100); // First 100 bytes MemorySegment slice2 = original.asSlice(100, 200); // Next 200 bytes MemorySegment slice3 = original.asSlice(300); // From offset 300 to end System.out.println("Original size: " + original.byteSize()); System.out.println("Slice1 size: " + slice1.byteSize()); System.out.println("Slice2 size: " + slice2.byteSize()); System.out.println("Slice3 size: " + slice3.byteSize()); // Verify slices point to same memory slice1.set(ValueLayout.JAVA_BYTE, 0, (byte) 255); byte valueFromOriginal = original.get(ValueLayout.JAVA_BYTE, 0); System.out.println("Value from original after modifying slice: " + valueFromOriginal); } } /** * Different arena types and their use cases */ public static void arenaTypes() { // Confined Arena - single thread, automatic cleanup try (Arena confined = Arena.ofConfined()) { MemorySegment segment1 = confined.allocate(100); System.out.println("Confined arena allocated: " + segment1.byteSize()); } // Shared Arena - multiple threads, manual control Arena shared = Arena.ofShared(); MemorySegment segment2 = shared.allocate(200); System.out.println("Shared arena allocated: " + segment2.byteSize()); shared.close(); // Manual cleanup required // Global Arena - JVM lifetime Arena global = Arena.global(); MemorySegment segment3 = global.allocate(50); System.out.println("Global arena allocated: " + segment3.byteSize()); // Global arena doesn't need closing } } Foreign Function Interface
package com.example.ffm; import jdk.incubator.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.file.Path; /** * Foreign Function Interface examples */ public class ForeignFunctions { /** * Basic C standard library function calls */ public static void callStandardLibraryFunctions() throws Throwable { // Get linker for standard C library Linker linker = Linker.nativeLinker(); SymbolLookup stdlib = linker.defaultLookup(); try (Arena arena = Arena.ofConfined()) { // Call strlen function MethodHandle strlen = linker.downcallHandle( stdlib.find("strlen").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS) ); // Allocate and initialize a string MemorySegment string = arena.allocateFrom("Hello, FFI!"); // Call strlen long length = (long) strlen.invoke(string); System.out.println("String length: " + length); // Call printf MethodHandle printf = linker.downcallHandle( stdlib.find("printf").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS) ); MemorySegment format = arena.allocateFrom("Formatted output: %s (length: %d)\n"); printf.invoke(format, string, length); } } /** * Calling mathematical functions */ public static void callMathFunctions() throws Throwable { Linker linker = Linker.nativeLinker(); SymbolLookup stdlib = linker.defaultLookup(); try (Arena arena = Arena.ofConfined()) { // Call pow function MethodHandle pow = linker.downcallHandle( stdlib.find("pow").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE) ); double result = (double) pow.invoke(2.0, 3.0); // 2^3 System.out.println("pow(2, 3) = " + result); // Call sqrt function MethodHandle sqrt = linker.downcallHandle( stdlib.find("sqrt").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_DOUBLE, ValueLayout.JAVA_DOUBLE) ); double sqrtResult = (double) sqrt.invoke(16.0); System.out.println("sqrt(16) = " + sqrtResult); } } /** * Working with custom native libraries */ public static void callCustomLibrary() throws Throwable { Linker linker = Linker.nativeLinker(); // Load custom library (example - you'd need to create this library) System.loadLibrary("mylib"); SymbolLookup mylib = SymbolLookup.loaderLookup(); try (Arena arena = Arena.ofConfined()) { // Example: Call a function that adds two integers MethodHandle addNumbers = linker.downcallHandle( mylib.find("add_numbers").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT) ); int sum = (int) addNumbers.invoke(15, 25); System.out.println("15 + 25 = " + sum); } } /** * String manipulation with C functions */ public static void stringManipulation() throws Throwable { Linker linker = Linker.nativeLinker(); SymbolLookup stdlib = linker.defaultLookup(); try (Arena arena = Arena.ofConfined()) { // Call strcpy function MethodHandle strcpy = linker.downcallHandle( stdlib.find("strcpy").orElseThrow(), FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS) ); // Allocate source and destination buffers MemorySegment source = arena.allocateFrom("Source string"); MemorySegment dest = arena.allocate(100); // Ensure enough space // Copy string strcpy.invoke(dest, source); // Verify copy String copiedString = dest.getUtf8String(0); System.out.println("Copied string: " + copiedString); } } } Advanced Memory Layouts
package com.example.ffm; import jdk.incubator.foreign.*; import java.lang.foreign.MemoryLayout; import java.lang.foreign.StructLayout; import java.lang.invoke.VarHandle; import java.util.List; /** * Advanced memory layout patterns */ public class AdvancedLayouts { /** * Complex struct with nested layouts */ public static void complexStructLayout() { try (Arena arena = Arena.ofConfined()) { // Define a Point struct GroupLayout pointLayout = MemoryLayout.structLayout( ValueLayout.JAVA_DOUBLE.withName("x"), ValueLayout.JAVA_DOUBLE.withName("y") ); // Define a Rectangle struct containing two Points GroupLayout rectLayout = MemoryLayout.structLayout( pointLayout.withName("topLeft"), pointLayout.withName("bottomRight"), ValueLayout.JAVA_INT.withName("color") ); // Allocate and initialize MemorySegment rect = arena.allocate(rectLayout); // Calculate offsets using VarHandle for type safety VarHandle topLeftXHandle = rectLayout.varHandle( MemoryLayout.PathElement.groupElement("topLeft"), MemoryLayout.PathElement.groupElement("x") ); VarHandle topLeftYHandle = rectLayout.varHandle( MemoryLayout.PathElement.groupElement("topLeft"), MemoryLayout.PathElement.groupElement("y") ); VarHandle colorHandle = rectLayout.varHandle( MemoryLayout.PathElement.groupElement("color") ); // Set values using VarHandle topLeftXHandle.set(rect, 0.0, 10.5); topLeftYHandle.set(rect, 0.0, 20.3); colorHandle.set(rect, 0.0, 0xFF0000); // Red // Read back double x = (double) topLeftXHandle.get(rect, 0.0); double y = (double) topLeftYHandle.get(rect, 0.0); int color = (int) colorHandle.get(rect, 0.0); System.out.printf("Rectangle: topLeft=(%.1f, %.1f), color=0x%X%n", x, y, color); } } /** * Union layout example */ public static void unionLayout() { try (Arena arena = Arena.ofConfined()) { // Define a union that can hold either int, double, or boolean GroupLayout unionLayout = MemoryLayout.unionLayout( ValueLayout.JAVA_INT.withName("asInt"), ValueLayout.JAVA_DOUBLE.withName("asDouble"), ValueLayout.JAVA_BOOLEAN.withName("asBoolean") ); MemorySegment union = arena.allocate(unionLayout); // Store as int union.set(ValueLayout.JAVA_INT, 0, 42); System.out.println("As int: " + union.get(ValueLayout.JAVA_INT, 0)); // Store as double (overwrites the int) union.set(ValueLayout.JAVA_DOUBLE, 0, 3.14159); System.out.println("As double: " + union.get(ValueLayout.JAVA_DOUBLE, 0)); // The size is the largest member System.out.println("Union size: " + unionLayout.byteSize() + " bytes"); } } /** * Array of structs with padding */ public static void arrayOfStructsWithPadding() { try (Arena arena = Arena.ofConfined()) { // Struct with padding for alignment GroupLayout paddedStruct = MemoryLayout.structLayout( ValueLayout.JAVA_BYTE.withName("type"), MemoryLayout.paddingLayout(3), // 3 bytes padding for 4-byte alignment ValueLayout.JAVA_INT.withName("data"), ValueLayout.JAVA_DOUBLE.withName("value") ); SequenceLayout arrayLayout = MemoryLayout.sequenceLayout(10, paddedStruct); MemorySegment array = arena.allocate(arrayLayout); System.out.println("Struct size: " + paddedStruct.byteSize()); System.out.println("Array size: " + arrayLayout.byteSize()); // Initialize array for (int i = 0; i < 10; i++) { long offset = i * paddedStruct.byteSize(); array.set(ValueLayout.JAVA_BYTE, offset, (byte) i); array.set(ValueLayout.JAVA_INT, offset + 4, i * 100); array.set(ValueLayout.JAVA_DOUBLE, offset + 8, i * 1.5); } } } /** * Memory layout with bit fields */ public static void bitFieldLayout() { try (Arena arena = Arena.ofConfined()) { // Simulate C bit fields using Java int with bit manipulation GroupLayout flagsLayout = MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("flags") ); MemorySegment flags = arena.allocate(flagsLayout); // Define bit positions int READ_FLAG = 1 << 0; // 0001 int WRITE_FLAG = 1 << 1; // 0010 int EXECUTE_FLAG = 1 << 2; // 0100 int HIDDEN_FLAG = 1 << 3; // 1000 // Set flags int currentFlags = 0; currentFlags |= READ_FLAG | WRITE_FLAG; // Set read and write flags flags.set(ValueLayout.JAVA_INT, 0, currentFlags); // Check flags int storedFlags = flags.get(ValueLayout.JAVA_INT, 0); boolean canRead = (storedFlags & READ_FLAG) != 0; boolean canWrite = (storedFlags & WRITE_FLAG) != 0; boolean canExecute = (storedFlags & EXECUTE_FLAG) != 0; boolean isHidden = (storedFlags & HIDDEN_FLAG) != 0; System.out.println("Permissions - Read: " + canRead + ", Write: " + canWrite + ", Execute: " + canExecute + ", Hidden: " + isHidden); } } } Performance-Critical Operations
package com.example.ffm; import jdk.incubator.foreign.*; import java.util.Arrays; /** * Performance-focused FFM usage patterns */ public class PerformanceOperations { /** * Bulk memory operations for performance */ public static void bulkMemoryOperations() { try (Arena arena = Arena.ofConfined()) { int size = 1000000; MemorySegment segment = arena.allocate(size * 4); // 1M integers long startTime = System.nanoTime(); // Fill with values using bulk operations for (int i = 0; i < size; i++) { segment.set(ValueLayout.JAVA_INT, i * 4L, i); } long fillTime = System.nanoTime() - startTime; System.out.printf("Fill time: %.2f ms%n", fillTime / 1_000_000.0); // Bulk copy to Java array startTime = System.nanoTime(); int[] javaArray = new int[size]; for (int i = 0; i < size; i++) { javaArray[i] = segment.get(ValueLayout.JAVA_INT, i * 4L); } long copyTime = System.nanoTime() - startTime; System.out.printf("Copy to Java array time: %.2f ms%n", copyTime / 1_000_000.0); // Verify first and last elements System.out.println("First element: " + javaArray[0]); System.out.println("Last element: " + javaArray[size - 1]); } } /** * Matrix operations using native memory */ public static void matrixOperations() { int rows = 100; int cols = 100; try (Arena arena = Arena.ofConfined()) { // Allocate matrices MemorySegment matrixA = arena.allocate(rows * cols * 8); // doubles MemorySegment matrixB = arena.allocate(rows * cols * 8); MemorySegment result = arena.allocate(rows * cols * 8); // Initialize matrices for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { long offset = (i * cols + j) * 8L; matrixA.set(ValueLayout.JAVA_DOUBLE, offset, i + j * 0.1); matrixB.set(ValueLayout.JAVA_DOUBLE, offset, i * 0.5 + j); } } // Matrix addition long startTime = System.nanoTime(); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { long offset = (i * cols + j) * 8L; double a = matrixA.get(ValueLayout.JAVA_DOUBLE, offset); double b = matrixB.get(ValueLayout.JAVA_DOUBLE, offset); result.set(ValueLayout.JAVA_DOUBLE, offset, a + b); } } long operationTime = System.nanoTime() - startTime; System.out.printf("Matrix addition time: %.2f ms%n", operationTime / 1_000_000.0); // Verify result long testOffset = (0 * cols + 0) * 8L; double testValue = result.get(ValueLayout.JAVA_DOUBLE, testOffset); System.out.println("Result[0][0]: " + testValue); } } /** * Memory-mapped file operations */ public static void memoryMappedFile() throws Exception { // Note: This is a simplified example // Real memory mapping would use FileChannel and MemorySegment.map try (Arena arena = Arena.ofConfined()) { // Simulate working with file-like data byte[] fileData = "This is simulated file content for memory mapping example".getBytes(); MemorySegment mappedSegment = arena.allocateFrom(fileData); // Work with the "mapped" memory System.out.println("File size: " + mappedSegment.byteSize() + " bytes"); // Read first few bytes as string String beginning = mappedSegment.getUtf8String(0); System.out.println("Beginning of file: " + beginning.substring(0, Math.min(20, beginning.length()))); // Modify content mappedSegment.set(ValueLayout.JAVA_BYTE, 0, (byte) 't'); // Change first character to lowercase String modified = mappedSegment.getUtf8String(0); System.out.println("Modified: " + modified.substring(0, Math.min(20, modified.length()))); } } } Integration with Java Ecosystem
package com.example.ffm; import jdk.incubator.foreign.*; import java.nio.ByteBuffer; import java.util.Arrays; /** * Integration with existing Java APIs */ public class JavaIntegration { /** * Convert between MemorySegment and ByteBuffer */ public static void memorySegmentToByteBuffer() { try (Arena arena = Arena.ofConfined()) { // Create MemorySegment and initialize MemorySegment segment = arena.allocate(100); for (int i = 0; i < 100; i++) { segment.set(ValueLayout.JAVA_BYTE, i, (byte) i); } // Convert to ByteBuffer ByteBuffer buffer = segment.asByteBuffer(); // Use with existing Java APIs byte[] javaArray = new byte[50]; buffer.get(javaArray, 0, 50); System.out.println("First 10 bytes from ByteBuffer:"); for (int i = 0; i < 10; i++) { System.out.print(javaArray[i] + " "); } System.out.println(); // Modify through ByteBuffer and see changes in MemorySegment buffer.put(0, (byte) 255); byte fromSegment = segment.get(ValueLayout.JAVA_BYTE, 0); System.out.println("Modified value via ByteBuffer: " + fromSegment); } } /** * Working with Java arrays and MemorySegment */ public static void arrayIntegration() { try (Arena arena = Arena.ofConfined()) { // From Java array to MemorySegment int[] javaArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; MemorySegment segment = arena.allocate(javaArray.length * 4L); // Copy array to segment for (int i = 0; i < javaArray.length; i++) { segment.set(ValueLayout.JAVA_INT, i * 4L, javaArray[i]); } // Process in native memory (example: double each value) for (int i = 0; i < javaArray.length; i++) { long offset = i * 4L; int value = segment.get(ValueLayout.JAVA_INT, offset); segment.set(ValueLayout.JAVA_INT, offset, value * 2); } // Copy back to Java array int[] resultArray = new int[javaArray.length]; for (int i = 0; i < javaArray.length; i++) { resultArray[i] = segment.get(ValueLayout.JAVA_INT, i * 4L); } System.out.println("Original: " + Arrays.toString(javaArray)); System.out.println("Processed: " + Arrays.toString(resultArray)); } } /** * String conversion utilities */ public static void stringUtilities() { try (Arena arena = Arena.ofConfined()) { // Java String to C string (null-terminated) String javaString = "Hello from Java!"; MemorySegment cString = arena.allocateFrom(javaString); // Get back as Java String String roundTrip = cString.getUtf8String(0); System.out.println("Round trip string: " + roundTrip); // Working with string arrays String[] stringArray = {"First", "Second", "Third"}; MemorySegment stringArraySegment = arena.allocate(stringArray.length * ValueLayout.ADDRESS.byteSize()); // Allocate each string and store pointers for (int i = 0; i < stringArray.length; i++) { MemorySegment stringSeg = arena.allocateFrom(stringArray[i]); stringArraySegment.set(ValueLayout.ADDRESS, i * ValueLayout.ADDRESS.byteSize(), stringSeg); } // Read back for (int i = 0; i < stringArray.length; i++) { MemorySegment stringPtr = stringArraySegment.get(ValueLayout.ADDRESS, i * ValueLayout.ADDRESS.byteSize()); String retrieved = stringPtr.getUtf8String(0); System.out.println("String[" + i + "]: " + retrieved); } } } } Error Handling and Safety
package com.example.ffm; import jdk.incubator.foreign.*; import java.lang.invoke.MethodHandle; /** * Error handling and safety patterns */ public class SafetyPatterns { /** * Safe memory access with bounds checking */ public static void safeMemoryAccess() { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(100); // Safe access within bounds try { segment.set(ValueLayout.JAVA_BYTE, 0, (byte) 42); // OK segment.set(ValueLayout.JAVA_BYTE, 99, (byte) 43); // OK // This would throw IndexOutOfBoundsException // segment.set(ValueLayout.JAVA_BYTE, 100, (byte) 44); System.out.println("All memory accesses were within bounds"); } catch (IndexOutOfBoundsException e) { System.err.println("Memory access out of bounds: " + e.getMessage()); } } } /** * Resource cleanup with try-with-resources */ public static void resourceCleanup() { // Arena implements AutoCloseable for automatic cleanup try (Arena arena = Arena.ofConfined()) { MemorySegment resource = arena.allocate(1024); // Use the resource... resource.set(ValueLayout.JAVA_INT, 0, 123); System.out.println("Resource allocated and will be automatically cleaned up"); } // Arena closed here, memory freed System.out.println("Resources have been cleaned up"); } /** * Handling native function call errors */ public static void safeNativeCalls() throws Throwable { Linker linker = Linker.nativeLinker(); SymbolLookup stdlib = linker.defaultLookup(); try (Arena arena = Arena.ofConfined()) { MethodHandle strlen = linker.downcallHandle( stdlib.find("strlen").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS) ); // Safe call with null check MemorySegment validString = arena.allocateFrom("Valid string"); long length = (long) strlen.invoke(validString); System.out.println("Valid string length: " + length); // What happens with null? This would likely crash in C, but FFM provides safety try { MemorySegment nullSegment = MemorySegment.NULL; // This should be handled safely by the FFM API // strlen.invoke(nullSegment); } catch (Exception e) { System.err.println("Safe handling of null pointer: " + e.getMessage()); } } } /** * Memory alignment safety */ public static void alignmentSafety() { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(100); // Properly aligned access segment.set(ValueLayout.JAVA_INT, 0, 100); // 4-byte aligned // Misaligned access - FFM handles this safely try { segment.set(ValueLayout.JAVA_INT, 1, 200); // Misaligned System.out.println("Misaligned access succeeded (platform may support it)"); } catch (Exception e) { System.out.println("Misaligned access prevented: " + e.getMessage()); } // Use VarHandle for guaranteed alignment VarHandle intHandle = ValueLayout.JAVA_INT.varHandle(); intHandle.set(segment, 0L, 300); // Always properly aligned System.out.println("Value set with VarHandle: " + intHandle.get(segment, 0L)); } } } Complete Example: Image Processing
package com.example.ffm; import jdk.incubator.foreign.*; import java.util.Arrays; /** * Complete example: Simple image processing using FFM API */ public class ImageProcessor { public static class Image { private final int width; private final int height; private final MemorySegment pixels; private final Arena arena; public Image(int width, int height) { this.width = width; this.height = height; this.arena = Arena.ofConfined(); this.pixels = arena.allocate(width * height * 3L); // 3 bytes per pixel (RGB) } public void setPixel(int x, int y, byte r, byte g, byte b) { long offset = (y * width + x) * 3L; pixels.set(ValueLayout.JAVA_BYTE, offset, r); pixels.set(ValueLayout.JAVA_BYTE, offset + 1, g); pixels.set(ValueLayout.JAVA_BYTE, offset + 2, b); } public byte[] getPixel(int x, int y) { long offset = (y * width + x) * 3L; return new byte[] { pixels.get(ValueLayout.JAVA_BYTE, offset), pixels.get(ValueLayout.JAVA_BYTE, offset + 1), pixels.get(ValueLayout.JAVA_BYTE, offset + 2) }; } public void toGrayscale() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { long offset = (y * width + x) * 3L; byte r = pixels.get(ValueLayout.JAVA_BYTE, offset); byte g = pixels.get(ValueLayout.JAVA_BYTE, offset + 1); byte b = pixels.get(ValueLayout.JAVA_BYTE, offset + 2); // Convert to grayscale byte gray = (byte) ((r & 0xFF) * 0.299 + (g & 0xFF) * 0.587 + (b & 0xFF) * 0.114); pixels.set(ValueLayout.JAVA_BYTE, offset, gray); pixels.set(ValueLayout.JAVA_BYTE, offset + 1, gray); pixels.set(ValueLayout.JAVA_BYTE, offset + 2, gray); } } } public void invertColors() { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { long offset = (y * width + x) * 3L; for (int i = 0; i < 3; i++) { byte component = pixels.get(ValueLayout.JAVA_BYTE, offset + i); pixels.set(ValueLayout.JAVA_BYTE, offset + i, (byte) (~component & 0xFF)); } } } } public void close() { arena.close(); } public int getWidth() { return width; } public int getHeight() { return height; } } public static void main(String[] args) { // Create a simple 3x3 image Image image = new Image(3, 3); // Set some pixels (simple pattern) image.setPixel(0, 0, (byte) 255, (byte) 0, (byte) 0); // Red image.setPixel(1, 0, (byte) 0, (byte) 255, (byte) 0); // Green image.setPixel(2, 0, (byte) 0, (byte) 0, (byte) 255); // Blue image.setPixel(0, 1, (byte) 255, (byte) 255, (byte) 0); // Yellow image.setPixel(1, 1, (byte) 255, (byte) 255, (byte) 255); // White image.setPixel(2, 1, (byte) 0, (byte) 255, (byte) 255); // Cyan image.setPixel(0, 2, (byte) 255, (byte) 0, (byte) 255); // Magenta image.setPixel(1, 2, (byte) 128, (byte) 128, (byte) 128); // Gray image.setPixel(2, 2, (byte) 0, (byte) 0, (byte) 0); // Black System.out.println("Original image:"); printImage(image); // Apply grayscale image.toGrayscale(); System.out.println("\nGrayscale image:"); printImage(image); // Apply color inversion image.invertColors(); System.out.println("\nInverted image:"); printImage(image); image.close(); } private static void printImage(Image image) { for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { byte[] pixel = image.getPixel(x, y); System.out.printf("[%3d,%3d,%3d] ", pixel[0] & 0xFF, pixel[1] & 0xFF, pixel[2] & 0xFF); } System.out.println(); } } } Best Practices and Considerations
- Use Arenas: Always use
Arenafor automatic resource management - Bounds Checking: Leverage FFM's built-in bounds checking
- Type Safety: Use
VarHandlefor type-safe memory access - Error Handling: Always handle potential native call failures
- Performance: Use bulk operations for better performance
- Testing: Thoroughly test with different data patterns and sizes
- Platform Differences: Be aware of platform-specific memory alignment
Conclusion
The Foreign Function & Memory API provides a powerful, safe, and efficient way to work with native memory and functions from Java. Key benefits include:
- Performance: Direct memory access without JNI overhead
- Safety: Built-in bounds checking and type validation
- Productivity: Pure Java API without native code generation
- Modern: Integration with modern Java features like pattern matching
- Future-proof: Designed for upcoming Java features like Valhalla
This API represents a significant step forward in Java's ability to efficiently interoperate with native code while maintaining Java's safety guarantees and developer productivity.