BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News JEP 456: Preparing for the Removal of Unsafe Memory-Access Methods

JEP 456: Preparing for the Removal of Unsafe Memory-Access Methods

JEP 471, Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal, has been delivered for JDK 23. This JEP proposes to deprecate the memory access methods in the Unsafe class for removal in a future release. These unsupported methods have been superseded by standard APIs, namely, JEP 193, Variable Handles, delivered in JDK 9; and JEP 454, Foreign Function & Memory API, delivered in JDK 22.

The primary goal of this deprecation is to prepare the ecosystem for the eventual removal of sun.misc.Unsafe's memory-access methods. By highlighting their usage through compile-time and runtime warnings, developers can identify and migrate to supported replacements. This transition aims to ensure that applications can smoothly adapt to modern JDK releases, enhancing security and performance.

Two standard APIs now provide safe and efficient alternatives to sun.misc.Unsafe. The VarHandle API, delivered in JDK 9 through JEP 193, offers methods to safely manipulate on-heap memory, ensuring operations are performed efficiently and without undefined behaviour. The Foreign Function & Memory API, delivered in JDK 22 through JEP 454, provides safe off-heap memory access methods, often used in conjunction with VarHandle to manage memory inside and outside the JVM heap. These APIs promise no undefined behaviour, long-term stability, and better integration with Java tooling and documentation.

The deprecated sun.misc.Unsafe methods fall into three categories: on-heap, off-heap, and bimodal (methods that can access both on-heap and off-heap memory). The on-heap methods are:

long objectFieldOffset(Field f)
long staticFieldOffset(Field f)
Object staticFieldBase(Field f)
int arrayBaseOffset(Class<?> arrayClass)
int arrayIndexScale(Class<?> arrayClass)

These methods can be replaced by VarHandle and MemorySegment::ofArray with its overloaded methods. For example, consider the following example:

class Foo {

    private static final Unsafe UNSAFE = ...;    // A sun.misc.Unsafe object

    private static final long X_OFFSET;

    static {
        try {
            X_OFFSET = UNSAFE.objectFieldOffset(Foo.class.getDeclaredField("x"));
        } catch (Exception ex) { throw new AssertionError(ex); }
    }

    private int x;

    public boolean tryToDoubleAtomically() {
        int oldValue = x;
        return UNSAFE.compareAndSwapInt(this, X_OFFSET, oldValue, oldValue * 2);
    }
}

This above code can be implemented using VarHandle as follows:

class Foo {

    private static final VarHandle X_VH;

    static {
        try {
            X_VH = MethodHandles.lookup().findVarHandle(Foo.class, "x", int.class);
        } catch (Exception ex) { throw new AssertionError(ex); }
    }

    private int x;

    public boolean tryAtomicallyDoubleX() {
        int oldValue = x;
        return X_VH.compareAndSet(this, oldValue, oldValue * 2);
    }
}

The off-heap methods are primarily as follows:

long allocateMemory(long bytes)
long reallocateMemory(long address, long bytes)
void freeMemory(long address)
void invokeCleaner(java.nio.ByteBuffer directBuffer)
void setMemory(long address, long bytes, byte value)
void copyMemory(long srcAddress, long destAddress, long bytes)
[type] get[Type](long address)
void put[Type](long address, [type] x)

These methods can be replaced by MemorySegment operations. Consider the following example:

class OffHeapIntBuffer {

    private static final Unsafe UNSAFE = ...;

    private static final int ARRAY_BASE = UNSAFE.arrayBaseOffset(int[].class);
    private static final int ARRAY_SCALE = UNSAFE.arrayIndexScale(int[].class);

    private final long size;
    private long bufferPtr;

    public OffHeapIntBuffer(long size) {
        this.size = size;
        this.bufferPtr = UNSAFE.allocateMemory(size * ARRAY_SCALE);
    }

    public void deallocate() {
        if (bufferPtr == 0) return;
        UNSAFE.freeMemory(bufferPtr);
        bufferPtr = 0;
    }

    private boolean checkBounds(long index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException(index);
        return true;
    }

    public void setVolatile(long index, int value) {
        checkBounds(index);
        UNSAFE.putIntVolatile(null, bufferPtr + ARRAY_SCALE * index, value);
    }

    public void initialize(long start, long n) {
        checkBounds(start);
        checkBounds(start + n-1);
        UNSAFE.setMemory(bufferPtr + start * ARRAY_SCALE, n * ARRAY_SCALE, 0);
    }

    public int[] copyToNewArray(long start, int n) {
        checkBounds(start);
        checkBounds(start + n-1);
        int[] a = new int[n];
        UNSAFE.copyMemory(null, bufferPtr + start * ARRAY_SCALE, a, ARRAY_BASE, n * ARRAY_SCALE);
        return a;
    }

}

This above can be replaced by using the standard Arena and MemorySegment APIs:

class OffHeapIntBuffer {

    private static final VarHandle ELEM_VH = ValueLayout.JAVA_INT.arrayElementVarHandle();

    private final Arena arena;
    private final MemorySegment buffer;

    public OffHeapIntBuffer(long size) {
        this.arena  = Arena.ofShared();
        this.buffer = arena.allocate(ValueLayout.JAVA_INT, size);
    }

    public void deallocate() {
        arena.close();
    }

    public void setVolatile(long index, int value) {
        ELEM_VH.setVolatile(buffer, 0L, index, value);
    }

    public void initialize(long start, long n) {
        buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
                       ValueLayout.JAVA_INT.byteSize() * n)
              .fill((byte) 0);
    }

    public int[] copyToNewArray(long start, int n) {
        return buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
                              ValueLayout.JAVA_INT.byteSize() * n)
                     .toArray(ValueLayout.JAVA_INT);
    }

}

The migration will occur in several phases, each aligned with a separate JDK release. In Phase 1, starting with JDK 23, all memory-access methods will be deprecated, and compile-time warnings will be issued. Phase 2, planned for JDK 25 or earlier, will introduce runtime warnings whenever the deprecated methods are used. Phase 3, scheduled for JDK 26 or later, will escalate the response by throwing exceptions by default when these methods are invoked. Finally, Phases 4 and 5 will remove the deprecated methods, potentially occurring in the same release.

Developers can use the new command-line option --sun-misc-unsafe-memory-access={allow|warn|debug|deny} to manage the deprecation warnings and assess the impact on their applications.

The deprecation of sun.misc.Unsafe memory-access methods are a significant step towards enhancing the integrity and security of the Java Platform. By adopting the VarHandle and Foreign Function & Memory APIs, developers can ensure their applications remain robust and compatible with future JDK releases. The phased approach provides ample time for migration, minimizing disruption while promoting best practices in Java development.

About the Author

Rate this Article

Adoption
Style

BT