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.