12.1 Arena Lifecycle Management and Memory Allocation

Master memory lifecycle control with Arena for safe, efficient off-heap memory management.

Understanding Arena Types

Arena Fundamentals:

import java.lang.foreign.*;

public class ArenaTypes {
    // Confined arena - thread-bound, fastest
    public static void confinedExample() {
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment segment = arena.allocate(1024);

            // Only this thread can access
            segment.set(ValueLayout.JAVA_INT, 0, 42);
            int value = segment.get(ValueLayout.JAVA_INT, 0);

            System.out.println("Value: " + value);
        } // Automatically freed
    }

    // Shared arena - thread-safe, slower
    public static void sharedExample() {
        try (Arena arena = Arena.ofShared()) {
            MemorySegment segment = arena.allocate(1024);

            // Multiple threads can safely access
            Thread t1 = new Thread(() -> {
                segment.set(ValueLayout.JAVA_INT, 0, 100);
            });

            Thread t2 = new Thread(() -> {
                int val = segment.get(ValueLayout.JAVA_INT, 0);
                System.out.println("Read: " + val);
            });

            t1.start();
            t2.start();

            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    // Global arena - never freed, use sparingly
    public static void globalExample() {
        Arena arena = Arena.global();

        // Lives forever - no try-with-resources
        MemorySegment segment = arena.allocate(1024);

        // Use for application-lifetime data
        segment.set(ValueLayout.JAVA_INT, 0, 999);

        // Never closed - memory persists until JVM shutdown
    }
}

Arena Lifecycle Patterns

Pattern 1: Request Scope:

public class RequestScopedMemory {
    public void handleRequest(HttpRequest request) {
        try (Arena arena = Arena.ofConfined()) {
            // All allocations for this request
            MemorySegment buffer = arena.allocate(8192);

            // Parse request data
            parseRequestBody(arena, request.body());

            // Process
            MemorySegment response = buildResponse(arena);

            // Send response
            sendResponse(response);

        } // All memory freed when request completes
    }

    private void parseRequestBody(Arena arena, byte[] body) {
        MemorySegment bodySegment = arena.allocateArray(
            ValueLayout.JAVA_BYTE,
            body
        );
        // Parse using bodySegment
    }

    private MemorySegment buildResponse(Arena arena) {
        MemorySegment response = arena.allocate(4096);
        // Build response
        return response;
    }

    private void sendResponse(MemorySegment response) {
        // Send to client
    }
}

Pattern 2: Session Scope:

public class Session implements AutoCloseable {
    private final Arena arena;
    private final MemorySegment sessionData;

    public Session(int dataSize) {
        // Shared arena for multi-threaded session access
        this.arena = Arena.ofShared();
        this.sessionData = arena.allocate(dataSize);

        // Initialize session
        sessionData.set(ValueLayout.JAVA_LONG, 0, System.currentTimeMillis());
    }

    public void storeValue(String key, int value) {
        // Store in session memory
        // Implementation uses sessionData
    }

    public int getValue(String key) {
        // Retrieve from session memory
        return 0;
    }

    @Override
    public void close() {
        arena.close();
    }
}

// Usage
public class SessionManager {
    public void processSession() {
        try (Session session = new Session(16384)) {
            session.storeValue("userId", 42);
            session.storeValue("requestCount", 10);

            int userId = session.getValue("userId");
            // Process session
        } // Session memory freed
    }
}

Pattern 3: Pooled Arena:

import java.util.concurrent.ConcurrentLinkedQueue;

public class ArenaPool {
    private final ConcurrentLinkedQueue<Arena> pool = new ConcurrentLinkedQueue<>();
    private final int maxPoolSize;
    private final long arenaSize;

    public ArenaPool(int maxPoolSize, long arenaSize) {
        this.maxPoolSize = maxPoolSize;
        this.arenaSize = arenaSize;
    }

    public Arena acquire() {
        Arena arena = pool.poll();
        if (arena == null) {
            arena = Arena.ofShared();
        }
        return arena;
    }

    public void release(Arena arena) {
        if (pool.size() < maxPoolSize) {
            // Reset arena if possible, or close and let acquire create new
            pool.offer(arena);
        } else {
            arena.close();
        }
    }

    public void shutdown() {
        Arena arena;
        while ((arena = pool.poll()) != null) {
            arena.close();
        }
    }
}

// Usage
public class PooledMemoryProcessor {
    private final ArenaPool pool = new ArenaPool(10, 1024 * 1024);

    public void process(byte[] data) {
        Arena arena = pool.acquire();
        try {
            MemorySegment segment = arena.allocate(data.length);
            MemorySegment.copy(
                data, 0,
                segment, ValueLayout.JAVA_BYTE, 0,
                data.length
            );

            // Process segment

        } finally {
            pool.release(arena);
        }
    }
}

Memory Allocation Strategies

1. Single Allocation:

public class SingleAllocation {
    public void allocateOne() {
        try (Arena arena = Arena.ofConfined()) {
            // Allocate single block
            MemorySegment segment = arena.allocate(1024);

            // Use segment
            segment.set(ValueLayout.JAVA_INT, 0, 42);
        }
    }
}

2. Multiple Small Allocations:

public class MultipleAllocations {
    public void allocateMany() {
        try (Arena arena = Arena.ofConfined()) {
            // Arena amortizes allocation cost
            MemorySegment seg1 = arena.allocate(16);
            MemorySegment seg2 = arena.allocate(32);
            MemorySegment seg3 = arena.allocate(64);

            // All freed together
        }
    }
}

3. Array Allocation:

public class ArrayAllocation {
    public void allocateArrays() {
        try (Arena arena = Arena.ofConfined()) {
            // Allocate int array
            int[] values = {1, 2, 3, 4, 5};
            MemorySegment intArray = arena.allocateArray(
                ValueLayout.JAVA_INT,
                values
            );

            // Allocate empty array
            MemorySegment emptyArray = arena.allocateArray(
                ValueLayout.JAVA_LONG,
                10  // 10 longs
            );

            // Allocate byte array
            byte[] bytes = "Hello".getBytes();
            MemorySegment byteArray = arena.allocateArray(
                ValueLayout.JAVA_BYTE,
                bytes
            );
        }
    }
}

4. String Allocation:

public class StringAllocation {
    public void allocateStrings() {
        try (Arena arena = Arena.ofConfined()) {
            // UTF-8 C string (null-terminated)
            MemorySegment cString = arena.allocateUtf8String("Hello, FFM!");

            // Read back
            String str = cString.getUtf8String(0);
            System.out.println("String: " + str);

            // Multiple strings
            String[] strings = {"First", "Second", "Third"};
            MemorySegment[] segments = new MemorySegment[strings.length];

            for (int i = 0; i < strings.length; i++) {
                segments[i] = arena.allocateUtf8String(strings[i]);
            }
        }
    }
}

5. Structured Allocation:

public class StructuredAllocation {
    private static final MemoryLayout PERSON_LAYOUT = MemoryLayout.structLayout(
        ValueLayout.JAVA_INT.withName("id"),
        MemoryLayout.paddingLayout(4),  // Padding for alignment
        ValueLayout.JAVA_LONG.withName("timestamp"),
        MemoryLayout.sequenceLayout(32, ValueLayout.JAVA_BYTE).withName("name")
    );

    public void allocateStruct() {
        try (Arena arena = Arena.ofConfined()) {
            // Allocate single struct
            MemorySegment person = arena.allocate(PERSON_LAYOUT);

            // Allocate array of structs
            MemorySegment people = arena.allocate(
                PERSON_LAYOUT.byteSize() * 10
            );

            // Access individual structs
            for (int i = 0; i < 10; i++) {
                long offset = i * PERSON_LAYOUT.byteSize();
                MemorySegment personSlice = people.asSlice(offset, PERSON_LAYOUT.byteSize());

                // Initialize struct
                personSlice.set(ValueLayout.JAVA_INT, 0, i);
                personSlice.set(ValueLayout.JAVA_LONG, 8, System.currentTimeMillis());
            }
        }
    }
}

Alignment and Padding

Understanding Alignment:

public class AlignmentExamples {
    public void demonstrateAlignment() {
        try (Arena arena = Arena.ofConfined()) {
            // Allocate with specific alignment
            MemorySegment aligned = arena.allocate(
                100,     // Size
                16       // Alignment (16-byte aligned)
            );

            // Address is guaranteed to be multiple of 16
            long address = aligned.address();
            System.out.println("Address: 0x" + Long.toHexString(address));
            System.out.println("Aligned: " + (address % 16 == 0));
        }
    }

    // Struct with padding
    private static final MemoryLayout ALIGNED_STRUCT = MemoryLayout.structLayout(
        ValueLayout.JAVA_BYTE.withName("flag"),
        MemoryLayout.paddingLayout(3),  // Pad to 4-byte boundary
        ValueLayout.JAVA_INT.withName("value"),
        MemoryLayout.paddingLayout(4),  // Pad to 8-byte boundary (for long)
        ValueLayout.JAVA_LONG.withName("timestamp")
    );

    public void allocateAlignedStruct() {
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment struct = arena.allocate(ALIGNED_STRUCT);

            // Fields are properly aligned
            struct.set(ValueLayout.JAVA_BYTE, 0, (byte) 1);
            struct.set(ValueLayout.JAVA_INT, 4, 42);
            struct.set(ValueLayout.JAVA_LONG, 12, 123456789L);
        }
    }
}

Real-World Example: Memory Pool Manager

import java.lang.foreign.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class MemoryPoolManager implements AutoCloseable {
    private final Arena arena;
    private final Map<String, MemorySegment> pools = new ConcurrentHashMap<>();
    private final long totalSize;
    private long allocated = 0;

    public MemoryPoolManager(long totalSize, boolean shared) {
        this.totalSize = totalSize;
        this.arena = shared ? Arena.ofShared() : Arena.ofConfined();
    }

    /**
     * Create a named memory pool
     */
    public MemorySegment createPool(String name, long size) {
        if (allocated + size > totalSize) {
            throw new OutOfMemoryError(
                "Cannot allocate " + size + " bytes. " +
                "Available: " + (totalSize - allocated)
            );
        }

        if (pools.containsKey(name)) {
            throw new IllegalArgumentException("Pool already exists: " + name);
        }

        MemorySegment pool = arena.allocate(size);
        pools.put(name, pool);
        allocated += size;

        System.out.println("Created pool '" + name + "': " + size + " bytes");
        System.out.println("Total allocated: " + allocated + "/" + totalSize);

        return pool;
    }

    /**
     * Get existing pool
     */
    public Optional<MemorySegment> getPool(String name) {
        return Optional.ofNullable(pools.get(name));
    }

    /**
     * Remove pool (doesn't actually free memory, just removes reference)
     */
    public void removePool(String name) {
        MemorySegment pool = pools.remove(name);
        if (pool != null) {
            allocated -= pool.byteSize();
            System.out.println("Removed pool '" + name + "'");
        }
    }

    /**
     * Get memory statistics
     */
    public MemoryStats getStats() {
        return new MemoryStats(
            totalSize,
            allocated,
            pools.size()
        );
    }

    public record MemoryStats(long total, long allocated, int poolCount) {
        public long available() {
            return total - allocated;
        }

        public double utilizationPercent() {
            return 100.0 * allocated / total;
        }

        @Override
        public String toString() {
            return String.format(
                "Memory: %d/%d bytes (%.1f%%), %d pools",
                allocated, total, utilizationPercent(), poolCount
            );
        }
    }

    @Override
    public void close() {
        pools.clear();
        arena.close();
        System.out.println("Memory pool manager closed");
    }

    // Example usage
    public static void main(String[] args) {
        try (MemoryPoolManager manager = new MemoryPoolManager(
            10 * 1024 * 1024,  // 10 MB total
            false               // Confined
        )) {
            // Create pools for different purposes
            MemorySegment requestPool = manager.createPool("requests", 2 * 1024 * 1024);
            MemorySegment cachePool = manager.createPool("cache", 5 * 1024 * 1024);
            MemorySegment tempPool = manager.createPool("temp", 1 * 1024 * 1024);

            // Use request pool
            requestPool.set(ValueLayout.JAVA_INT, 0, 12345);

            // Use cache pool
            cachePool.set(ValueLayout.JAVA_LONG, 0, System.currentTimeMillis());

            // Stats
            System.out.println(manager.getStats());

            // Cleanup temp pool
            manager.removePool("temp");
            System.out.println(manager.getStats());

        } // All memory freed
    }
}

Best Practices

1. Use Try-With-Resources:

// Good - automatic cleanup
public void goodPractice() {
    try (Arena arena = Arena.ofConfined()) {
        MemorySegment segment = arena.allocate(1024);
        // Use segment
    } // Guaranteed cleanup
}

// Bad - manual cleanup, error-prone
public void badPractice() {
    Arena arena = Arena.ofConfined();
    try {
        MemorySegment segment = arena.allocate(1024);
        // Use segment
    } finally {
        arena.close();  // Must remember to close
    }
}

2. Choose Appropriate Arena Type:

// Single-threaded processing - use confined
public void singleThreaded() {
    try (Arena arena = Arena.ofConfined()) {
        // Fastest, no synchronization overhead
    }
}

// Multi-threaded access - use shared
public void multiThreaded() {
    try (Arena arena = Arena.ofShared()) {
        // Thread-safe, necessary synchronization
    }
}

3. Avoid Segment Escape:

// Bad - segment escapes arena scope
public MemorySegment badEscape() {
    try (Arena arena = Arena.ofConfined()) {
        return arena.allocate(1024);  // UNSAFE!
    }
} // Arena closed, segment invalid

// Good - process within scope
public int goodNoEscape() {
    try (Arena arena = Arena.ofConfined()) {
        MemorySegment segment = arena.allocate(1024);
        segment.set(ValueLayout.JAVA_INT, 0, 42);
        return segment.get(ValueLayout.JAVA_INT, 0);  // Extract value
    }
}

4. Batch Allocations:

// Good - single arena for multiple allocations
public void batchAllocations() {
    try (Arena arena = Arena.ofConfined()) {
        List<MemorySegment> segments = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            segments.add(arena.allocate(64));
        }

        // Process all segments

    } // All freed at once
}

5. Validate Sizes:

public void validateSizes(long requestedSize) {
    if (requestedSize <= 0) {
        throw new IllegalArgumentException("Size must be positive");
    }

    if (requestedSize > Integer.MAX_VALUE) {
        throw new IllegalArgumentException("Size too large");
    }

    try (Arena arena = Arena.ofConfined()) {
        MemorySegment segment = arena.allocate(requestedSize);
        // Use segment
    }
}

These patterns and practices ensure safe, efficient memory management with Arena and MemorySegment.