Java Native Access (JNA) provides Java programs easy access to native shared libraries without writing anything but Java code - no JNI or native code is required.
—
This document covers JNA's memory management system, including the Pointer and Memory classes, type-safe memory operations, and by-reference parameter passing.
import com.sun.jna.Pointer;
import com.sun.jna.Memory;
import com.sun.jna.ptr.*;The Pointer class provides type-safe access to native memory locations:
/**
* Represents a pointer to native memory with type-safe access methods
*/
public class Pointer {
/**
* Read byte value from memory
* @param offset Byte offset from pointer base
* @return Byte value at offset
*/
public byte getByte(long offset);
/**
* Write byte value to memory
* @param offset Byte offset from pointer base
* @param value Byte value to write
*/
public void setByte(long offset, byte value);
/**
* Read short value from memory (2 bytes)
* @param offset Byte offset from pointer base
* @return Short value at offset
*/
public short getShort(long offset);
/**
* Write short value to memory
* @param offset Byte offset from pointer base
* @param value Short value to write
*/
public void setShort(long offset, short value);
/**
* Read int value from memory (4 bytes)
* @param offset Byte offset from pointer base
* @return Int value at offset
*/
public int getInt(long offset);
/**
* Write int value to memory
* @param offset Byte offset from pointer base
* @param value Int value to write
*/
public void setInt(long offset, int value);
/**
* Read long value from memory (8 bytes)
* @param offset Byte offset from pointer base
* @return Long value at offset
*/
public long getLong(long offset);
/**
* Write long value to memory
* @param offset Byte offset from pointer base
* @param value Long value to write
*/
public void setLong(long offset, long value);
/**
* Read float value from memory (4 bytes)
* @param offset Byte offset from pointer base
* @return Float value at offset
*/
public float getFloat(long offset);
/**
* Write float value to memory
* @param offset Byte offset from pointer base
* @param value Float value to write
*/
public void setFloat(long offset, float value);
/**
* Read double value from memory (8 bytes)
* @param offset Byte offset from pointer base
* @return Double value at offset
*/
public double getDouble(long offset);
/**
* Write double value to memory
* @param offset Byte offset from pointer base
* @param value Double value to write
*/
public void setDouble(long offset, double value);
/**
* Read pointer value from memory
* @param offset Byte offset from pointer base
* @return Pointer value at offset (may be null)
*/
public Pointer getPointer(long offset);
/**
* Write pointer value to memory
* @param offset Byte offset from pointer base
* @param value Pointer value to write (may be null)
*/
public void setPointer(long offset, Pointer value);
/**
* Read null-terminated string from memory
* @param offset Byte offset from pointer base
* @param encoding Character encoding (e.g., "UTF-8", "UTF-16")
* @return String read from memory
*/
public String getString(long offset, String encoding);
/**
* Write null-terminated string to memory
* @param offset Byte offset from pointer base
* @param value String to write
* @param encoding Character encoding
*/
public void setString(long offset, String value, String encoding);
/**
* Create shared view of memory region
* @param offset Byte offset from pointer base
* @param size Size of shared region in bytes
* @return New pointer sharing memory region
*/
public Pointer share(long offset, long size);
/**
* Get NIO ByteBuffer view of memory
* @param offset Byte offset from pointer base
* @param length Size of buffer in bytes
* @return ByteBuffer backed by native memory
*/
public java.nio.ByteBuffer getByteBuffer(long offset, long length);
}The Memory class extends Pointer to provide malloc-based native memory allocation:
/**
* Pointer to memory obtained from native heap with bounds checking
*/
public class Memory extends Pointer {
/**
* Allocate native memory block
* @param size Number of bytes to allocate
* @throws OutOfMemoryError if allocation fails
*/
public Memory(long size);
/**
* Get allocated memory size
* @return Size in bytes
*/
public long size();
/**
* Clear memory to zero
*/
public void clear();
/**
* Check if memory is still allocated
* @return true if memory is valid
*/
public boolean valid();
/**
* Create aligned memory view
* @param byteBoundary Alignment boundary (must be power of 2)
* @return New pointer aligned to boundary
*/
public Pointer align(int byteBoundary);
/**
* Explicitly dispose of memory
* Note: Memory is automatically freed by finalizer
*/
public void dispose();
// Static utility methods
/**
* Force cleanup of GC'd ByteBuffers
*/
public static void purge();
/**
* Dispose all allocated Memory objects
*/
public static void disposeAll();
}// Allocate 1KB buffer
Memory buffer = new Memory(1024);
// Write primitive values
buffer.setInt(0, 42);
buffer.setFloat(4, 3.14f);
buffer.setLong(8, 123456789L);
// Read values back
int intVal = buffer.getInt(0); // 42
float floatVal = buffer.getFloat(4); // 3.14f
long longVal = buffer.getLong(8); // 123456789L
// String operations with encoding
buffer.setString(16, "Hello World", "UTF-8");
String text = buffer.getString(16, "UTF-8"); // "Hello World"
// Pointer operations
Memory subBuffer = new Memory(256);
buffer.setPointer(100, subBuffer);
Pointer retrieved = buffer.getPointer(100);// Create large buffer
Memory mainBuffer = new Memory(1000);
// Create shared views of different regions
Pointer header = mainBuffer.share(0, 100); // First 100 bytes
Pointer data = mainBuffer.share(100, 800); // Next 800 bytes
Pointer footer = mainBuffer.share(900, 100); // Last 100 bytes
// Write to shared regions
header.setInt(0, 0xDEADBEEF); // Magic number in header
data.setString(0, "payload", "UTF-8");
footer.setInt(0, 0xCAFEBABE); // Magic number in footer
// Changes are visible in main buffer
int magic = mainBuffer.getInt(0); // 0xDEADBEEF
String payload = mainBuffer.getString(100, "UTF-8"); // "payload"// Get NIO ByteBuffer view
Memory buffer = new Memory(512);
java.nio.ByteBuffer nio = buffer.getByteBuffer(0, 512);
// Use NIO operations
nio.putInt(42);
nio.putFloat(3.14f);
nio.flip();
// Read back via buffer methods
int value = buffer.getInt(0); // 42
float fval = buffer.getFloat(4); // 3.14fJNA provides specialized classes for passing primitive values by reference:
import com.sun.jna.ptr.*;
/**
* Base class for all by-reference types
*/
public abstract class ByReference extends PointerType {
/**
* Create with specified data size
* @param dataSize Size in bytes of referenced data
*/
protected ByReference(int dataSize);
}
/**
* Reference to a byte value (char* in C)
*/
public class ByteByReference extends ByReference {
public ByteByReference();
public ByteByReference(byte value);
/**
* Set the referenced byte value
* @param value New byte value
*/
public void setValue(byte value);
/**
* Get the referenced byte value
* @return Current byte value
*/
public byte getValue();
}
/**
* Reference to a short value (short* in C)
*/
public class ShortByReference extends ByReference {
public ShortByReference();
public ShortByReference(short value);
public void setValue(short value);
public short getValue();
}
/**
* Reference to an int value (int* in C)
*/
public class IntByReference extends ByReference {
public IntByReference();
public IntByReference(int value);
public void setValue(int value);
public int getValue();
}
/**
* Reference to a long value (long* in C)
*/
public class LongByReference extends ByReference {
public LongByReference();
public LongByReference(long value);
public void setValue(long value);
public long getValue();
}
/**
* Reference to a float value (float* in C)
*/
public class FloatByReference extends ByReference {
public FloatByReference();
public FloatByReference(float value);
public void setValue(float value);
public float getValue();
}
/**
* Reference to a double value (double* in C)
*/
public class DoubleByReference extends ByReference {
public DoubleByReference();
public DoubleByReference(double value);
public void setValue(double value);
public double getValue();
}
/**
* Reference to a NativeLong value (long* on platform)
*/
public class NativeLongByReference extends ByReference {
public NativeLongByReference();
public NativeLongByReference(NativeLong value);
public void setValue(NativeLong value);
public NativeLong getValue();
}
/**
* Reference to a Pointer value (void** in C)
*/
public class PointerByReference extends ByReference {
public PointerByReference();
public PointerByReference(Pointer value);
public void setValue(Pointer value);
public Pointer getValue();
}// Define native function that modifies parameters
public interface MyLibrary extends Library {
MyLibrary INSTANCE = Native.loadLibrary("mylib", MyLibrary.class);
/**
* Function that increments an integer value
* void increment(int* value);
*/
void increment(IntByReference value);
/**
* Function that swaps two float values
* void swap_floats(float* a, float* b);
*/
void swap_floats(FloatByReference a, FloatByReference b);
/**
* Function that allocates memory and returns pointer
* int create_buffer(void** buffer, int size);
*/
int create_buffer(PointerByReference buffer, int size);
}
// Usage examples
IntByReference counter = new IntByReference(10);
MyLibrary.INSTANCE.increment(counter);
int newValue = counter.getValue(); // 11
FloatByReference a = new FloatByReference(1.0f);
FloatByReference b = new FloatByReference(2.0f);
MyLibrary.INSTANCE.swap_floats(a, b);
// Now a.getValue() == 2.0f and b.getValue() == 1.0f
PointerByReference bufferRef = new PointerByReference();
int result = MyLibrary.INSTANCE.create_buffer(bufferRef, 1024);
if (result == 0) {
Pointer buffer = bufferRef.getValue();
// Use the allocated buffer...
}/**
* Wide character string for Unicode support
*/
public final class WString implements CharSequence, Comparable<Object> {
/**
* Create wide string from regular string
* @param s String to convert
*/
public WString(String s);
/**
* Get string value
* @return String representation
*/
public String toString();
/**
* Get length in characters
* @return Number of characters
*/
public int length();
/**
* Get character at index
* @param index Character index
* @return Character at index
*/
public char charAt(int index);
/**
* Get subsequence
* @param start Start index (inclusive)
* @param end End index (exclusive)
* @return Character subsequence
*/
public CharSequence subSequence(int start, int end);
}
// String usage examples
Memory buffer = new Memory(256);
// ASCII strings
buffer.setString(0, "Hello", "ASCII");
String ascii = buffer.getString(0, "ASCII");
// UTF-8 strings
buffer.setString(50, "Hello 世界", "UTF-8");
String utf8 = buffer.getString(50, "UTF-8");
// Wide strings (platform-specific Unicode)
WString wide = new WString("Hello 世界");
// Pass to native function expecting wchar_t*/**
* Array of strings in native memory
*/
public class StringArray extends Memory {
/**
* Create from String array using default encoding
* @param strings Array of strings
*/
public StringArray(String[] strings);
/**
* Create from String array with encoding choice
* @param strings Array of strings
* @param wide true for wide character encoding
*/
public StringArray(String[] strings, boolean wide);
/**
* Create from WString array
* @param strings Array of wide strings
*/
public StringArray(WString[] strings);
}
// Usage example
String[] args = {"program", "--verbose", "input.txt"};
StringArray argv = new StringArray(args);
// Pass to native function expecting char**
public interface System extends Library {
int exec(String program, StringArray argv);
}// 1. Always check buffer bounds
Memory buffer = new Memory(1024);
if (offset + dataSize <= buffer.size()) {
buffer.setInt(offset, value);
}
// 2. Use try-with-resources pattern for explicit cleanup
class ManagedMemory extends Memory implements AutoCloseable {
public ManagedMemory(long size) { super(size); }
@Override
public void close() {
dispose();
}
}
try (ManagedMemory buffer = new ManagedMemory(1024)) {
// Use buffer...
} // Automatically disposed
// 3. Validate pointer before use
public void safeRead(Pointer ptr, long offset) {
if (ptr != null && ptr != Pointer.NULL) {
int value = ptr.getInt(offset);
// Use value...
}
}
// 4. Handle encoding explicitly
String text = "Hello 世界";
buffer.setString(0, text, "UTF-8"); // Explicit encoding
String result = buffer.getString(0, "UTF-8");
// 5. Use sharing for structured access
Memory packet = new Memory(100);
Pointer header = packet.share(0, 20); // 20-byte header
Pointer payload = packet.share(20, 80); // 80-byte payload// Memory alignment for performance
Memory buffer = new Memory(1000);
Pointer aligned = buffer.align(16); // 16-byte aligned access
// Zero memory efficiently
buffer.clear(); // Zeros entire buffer
// Bulk copy operations
Memory src = new Memory(100);
Memory dst = new Memory(100);
// Fill source with data...
src.write(0, dst.getByteArray(0, 100), 0, 100);
// Get raw bytes for processing
byte[] rawData = buffer.getByteArray(0, (int)buffer.size());
// Process rawData...
buffer.write(0, rawData, 0, rawData.length);try {
Memory buffer = new Memory(1024 * 1024 * 1024); // 1GB
// Use buffer...
} catch (OutOfMemoryError e) {
System.err.println("Failed to allocate native memory: " + e.getMessage());
}
// Check memory validity
Memory buffer = new Memory(1024);
if (buffer.valid()) {
buffer.setInt(0, 42);
} else {
throw new IllegalStateException("Buffer has been disposed");
}This comprehensive memory management system provides safe, efficient access to native memory while maintaining Java's type safety and automatic memory management principles.
Install with Tessl CLI
npx tessl i tessl/maven-net-java-dev-jna--jna