The class sun.misc.Unsafe is an internal JDK class that reads and writes memory without bounds or type checks, allocates memory outside the Java heap and performs compare-and-swap operations on fields. Its memory-access methods are deprecated for removal since Java 23 (JEP 471), and since Java 24 the JVM prints a warning the first time one of them runs (JEP 498).
Libraries such as serializers, network frameworks and concurrent data structures used Unsafe for speed before the JDK had a safe API for the same work. Today, field access and compare-and-swap go through VarHandle (Java 9), and off-heap memory goes through the Foreign Function and Memory (FFM) API, final since Java 22 (JEP 454).
The following example writes and reads a long outside the heap, once with Unsafe and once with the FFM API.
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long address = unsafe.allocateMemory(8);
unsafe.putLong(address, 42L); // deprecated for removal
long oldWay = unsafe.getLong(address); // 42
unsafe.freeMemory(address);
MemorySegment segment = Arena.ofAuto().allocate(ValueLayout.JAVA_LONG);
segment.set(ValueLayout.JAVA_LONG, 0, 42L);
long newWay = segment.get(ValueLayout.JAVA_LONG, 0); // 42
Notice that the Unsafe version works with a raw address that nothing checks, whereas the MemorySegment knows its size and who frees it. We look at what Unsafe offers and how code gets an instance, follow its deprecation from Java 23 to Java 27, and replace each use case with VarHandle or the FFM API.
1. What sun.misc.Unsafe Provides
The class lives in the module jdk.unsupported, which the JDK kept open after Java 9 so that existing libraries could still run. It is not part of the Java SE API, has no official Javadoc, and its methods can crash the JVM when they get a wrong address or offset. The JDK has its own copy, jdk.internal.misc.Unsafe, which application code cannot access.
On Java 25, 67 of the 70 public method names of sun.misc.Unsafe are deprecated for removal. Only allocateInstance(), pageSize() and throwException() carry no deprecation. The methods fall into a few groups, and each group has a supported replacement.
| Group | Example methods | Deprecated for removal since | Replacement |
|---|---|---|---|
| Field access and CAS on objects | objectFieldOffset, getInt, compareAndSwapInt, getAndAddInt | Java 18 (offsets), Java 23 (access) | VarHandle, atomic classes |
| Array element access | arrayBaseOffset, arrayIndexScale | Java 23 | MethodHandles.arrayElementVarHandle() |
| Off-heap memory | allocateMemory, putLong(address, …), copyMemory, freeMemory | Java 23 | Arena and MemorySegment (FFM API) |
| Fences and thread parking | fullFence, park, unpark | Java 22 | VarHandle.fullFence(), LockSupport |
| Direct buffer cleanup | invokeCleaner | Java 23 | Arena.close() for FFM segments |
| Object creation without a constructor | allocateInstance | Not deprecated | A constructor, factory or serialization framework |
2. Getting an Unsafe Instance
The static method Unsafe.getUnsafe() exists, but it throws SecurityException unless the caller was loaded by the boot class loader, which is never the case for application code. Libraries read the private static field theUnsafe with reflection instead, and the module jdk.unsupported allows that access.
Unsafe direct = Unsafe.getUnsafe(); // SecurityException: Unsafe
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe instance = (Unsafe) theUnsafe.get(null);
int pageSize = instance.pageSize(); // 4096
The compiler already warns at the import, with warning: Unsafe is internal proprietary API and may be removed in a future release. That warning has existed for years, and unlike normal deprecation warnings, @SuppressWarnings cannot turn it off.
3. Deprecation Timeline and Runtime Warnings
The JDK removes Unsafe in steps, so that library authors have several releases to move to the replacements. JEP 471 lists the steps, and each one lands in a separate release.

| Java version | Change |
|---|---|
| Java 9 | VarHandle added (JEP 193); sun.misc.Unsafe moves to the module jdk.unsupported |
| Java 22 | FFM API final (JEP 454); fences and park/unpark deprecated for removal |
| Java 23 | All memory-access methods deprecated for removal; option –sun-misc-unsafe-memory-access added with default allow |
| Java 24 | Default becomes warn, one warning on the first call (JEP 498) |
| Java 25 to Java 27 | Default is still warn |
| Later release | Default deny, calls throw UnsupportedOperationException; after that, the methods are removed |
The following program uses Unsafe for a counter, and on Java 25 it prints four warning lines on the first call before its own output.
java -cp classes LegacyCounter
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by LegacyCounter (file:/home/dev/app/classes/)
WARNING: Please consider reporting this to the maintainers of class LegacyCounter
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
views = 1
The option –sun-misc-unsafe-memory-access takes four values. The value allow hides the warning, warn prints it once, debug prints one line with a stack trace for every call, and deny makes every call fail the way it will in a future release.
java --sun-misc-unsafe-memory-access=deny -cp classes LegacyCounter
Exception in thread "main" java.lang.UnsupportedOperationException: objectFieldOffset
at jdk.unsupported/sun.misc.Unsafe.beforeMemoryAccessSlow(Unsafe.java:1822)
at jdk.unsupported/sun.misc.Unsafe.beforeMemoryAccess(Unsafe.java:1787)
at jdk.unsupported/sun.misc.Unsafe.objectFieldOffset(Unsafe.java:905)
Running the test suite with deny today shows which code paths will break when the default changes. The flag allow only hides the problem until the methods are gone.
4. Replacing Field Access and CAS With VarHandle
A VarHandle is a typed reference to a field, a static field or an array element that supports plain, volatile, acquire/release and atomic access. It replaces the pair of objectFieldOffset() and compareAndSwapInt(), and the JVM checks the type and the receiver on every call, so a wrong object throws an exception instead of corrupting memory.
A web analytics service counts page views in millions of small PageStats objects. One AtomicInteger per field would double the number of objects, so the class keeps a plain volatile int and updates it through one static VarHandle, which is how libraries used Unsafe before.
static class PageStats {
private static final VarHandle VIEWS;
static {
try {
VIEWS = MethodHandles.lookup().findVarHandle(PageStats.class, "views", int.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
private volatile int views;
void recordView() {
VIEWS.getAndAdd(this, 1);
}
boolean resetIf(int expected) {
return VIEWS.compareAndSet(this, expected, 0);
}
int views() {
return views;
}
}
PageStats stats = new PageStats();
IntStream.range(0, 10_000).parallel().forEach(i -> stats.recordView());
int counted = stats.views(); // 10000
boolean staleReset = stats.resetIf(9_999); // false
boolean reset = stats.resetIf(10_000); // true
int afterReset = stats.views(); // 0
Ten thousand parallel increments give a count of 10,000, so getAndAdd() is atomic in the same way as Unsafe.getAndAddInt(). The method names map almost one to one.
| sun.misc.Unsafe | VarHandle |
|---|---|
| objectFieldOffset(field) | MethodHandles.lookup().findVarHandle(Owner.class, “name”, type) |
| getIntVolatile(obj, offset) | handle.getVolatile(obj) |
| putOrderedInt(obj, offset, v) | handle.setRelease(obj, v) |
| compareAndSwapInt(obj, offset, e, v) | handle.compareAndSet(obj, e, v) |
| getAndAddInt(obj, offset, d) | handle.getAndAdd(obj, d) |
| arrayBaseOffset + arrayIndexScale | MethodHandles.arrayElementVarHandle(int[].class) |
| fullFence() | VarHandle.fullFence() |
For a single counter, AtomicInteger is simpler. The theory behind compareAndSet() is covered in compare-and-swap, and the visibility rules of the access modes come from the Java memory model.
Arrays work the same way with an element handle, which also replaces the manual address arithmetic with arrayBaseOffset().
VarHandle slots = MethodHandles.arrayElementVarHandle(int[].class);
int[] seats = new int[4];
boolean booked = slots.compareAndSet(seats, 2, 0, 1); // true
boolean bookedTwice = slots.compareAndSet(seats, 2, 0, 1); // false
int outside = (int) slots.getVolatile(seats, 4); // ArrayIndexOutOfBoundsException
5. Replacing Off-Heap Memory With the FFM API
The FFM API in java.lang.foreign allocates memory outside the heap through an Arena, which decides when the memory is freed, and returns a MemorySegment, which knows its address and size. Every access is checked against the bounds of the segment and against the lifetime of the arena, so the errors that crash the JVM with Unsafe become exceptions.
A price feed keeps the latest bid and ask for 100,000 instruments in a fixed-size off-heap table, so the garbage collector does not scan the data. With Unsafe, the code allocated one block and computed addresses by hand. With FFM, the arena owns the block and closes it at the end of the try block.
static long lastAsk(int instruments, int instrument, long ask) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment table = arena.allocate(ValueLayout.JAVA_LONG, instruments * 2L);
table.setAtIndex(ValueLayout.JAVA_LONG, instrument * 2L + 1, ask);
return table.getAtIndex(ValueLayout.JAVA_LONG, instrument * 2L + 1);
}
}
long ask = lastAsk(100_000, 7, 10_125L); // 10125
long tableBytes = Arena.ofAuto().allocate(ValueLayout.JAVA_LONG, 200_000L).byteSize(); // 1600000
long outOfRange = lastAsk(100_000, 100_000, 1L); // IndexOutOfBoundsException
The bounds check that throws IndexOutOfBoundsException in the last line would have been a silent write past the end of the block with Unsafe. Segment sizes and offsets are long values, so a segment can be larger than 2 GB, which was the reason for the “big array” trick with allocateMemory(). A segment used after its arena is closed throws IllegalStateException instead of reading freed memory.
Arena shortLived = Arena.ofConfined();
MemorySegment buffer = shortLived.allocate(16);
shortLived.close();
byte afterClose = buffer.get(ValueLayout.JAVA_BYTE, 0); // IllegalStateException: Already closed
| Unsafe call | FFM replacement |
|---|---|
| allocateMemory(size) | arena.allocate(size) |
| putLong(address, v) / getLong(address) | segment.set(JAVA_LONG, offset, v) / segment.get(JAVA_LONG, offset) |
| copyMemory(src, dst, n) | MemorySegment.copy(src, srcOffset, dst, dstOffset, n) |
| setMemory(address, n, value) | segment.fill(value) |
| freeMemory(address) | arena.close(), or garbage collection with Arena.ofAuto() |
| invokeCleaner(directBuffer) | segment.asByteBuffer() on a segment from a closeable arena |
The same API maps files into memory with FileChannel.map(mode, offset, size, arena), which returns a MemorySegment. That replaces both Unsafe address tricks and the 2 GB limit of MappedByteBuffer.
6. Old Unsafe Tricks and Their Safe Versions
Many blog posts from the Java 7 era show Unsafe tricks that are broken, unsafe or unnecessary on Java 25. Each one has a plain Java answer.
6.1. Creating an Object Without Calling the Constructor
The method allocateInstance() creates an object and skips every constructor and field initializer, so all fields keep their default values. It is not deprecated, and serialization and mocking libraries still use it, but in application code it produces objects that break their own rules.
static class Ticket {
private final String event;
Ticket(String event) {
this.event = Objects.requireNonNull(event);
}
String event() {
return event;
}
}
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe u = (Unsafe) unsafeField.get(null);
Ticket blank = (Ticket) u.allocateInstance(Ticket.class);
String noEvent = blank.event(); // null
The constructor rejects a null event, yet the object exists with one. The same trick creates a second instance of a singleton with a private constructor. An enum singleton blocks Constructor.newInstance() and serialization copies, but even an enum class gets a second, broken constant from allocateInstance() on Java 25, with null as its name.
6.2. Wiping a Password String
Old examples copy question marks over the memory of a String to erase a password. On current JDKs, the characters of a String sit in a byte[] that the object only references, so copying over the object writes into the wrong place and can corrupt the heap. Strings are immutable by design, and the safe way is to keep secrets in a char[] or byte[] and clear it with Arrays.fill().
char[] password = {'s', 'e', 'c', 'r', 'e', 't'};
Arrays.fill(password, '\0');
int nonZero = (int) new String(password).chars().filter(c -> c != 0).count(); // 0
6.3. Defining Classes at Runtime
The method Unsafe.defineClass() was removed in Java 11, and defineAnonymousClass() was removed in Java 17. The supported versions are MethodHandles.Lookup.defineClass() for a normal class in the same package, and Lookup.defineHiddenClass() for classes that frameworks generate and unload, as lambdas and proxies do.
6.4. Shallow Copies by Copying Memory
Copying the bytes of one object onto another skips the object header rules, the garbage collector barriers and the field layout, which changes between JDK versions and between garbage collectors. A copy constructor, a static factory or a record with a with-style method gives the same result, and the JIT compiler makes a field-by-field copy fast.
7. Finding Unsafe in Your Dependencies
Most applications never call Unsafe themselves, and the warning comes from a library. The tool jdeps lists every class that uses a JDK internal API, and the debug mode of the JVM option shows the call site with a stack trace.
jdeps --jdk-internals classes/LegacyCounter.class
java --sun-misc-unsafe-memory-access=debug -cp classes LegacyCounter
LegacyCounter.class -> jdk.unsupported
LegacyCounter -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
WARNING: sun.misc.Unsafe::objectFieldOffset called by LegacyCounter (file:/home/dev/app/classes/)
at LegacyCounter.main(LegacyCounter.java:10)
WARNING: sun.misc.Unsafe::getAndAddInt called by LegacyCounter (file:/home/dev/app/classes/)
at LegacyCounter.main(LegacyCounter.java:12)
For a JAR, we run jdeps –jdk-internals -cp ‘lib/*’ app.jar on the whole classpath. When the caller is a library, we upgrade it to a version that uses VarHandle and FFM, or report the call to its maintainers, as the warning itself asks.
8. sun.misc.Unsafe FAQs
After a JDK upgrade prints the Unsafe warning, teams want to know whether it breaks anything and how to remove it.
8.1. Is sun.misc.Unsafe removed in Java 25?
No. The class and its methods still work on Java 25, 26 and 27, and the memory-access methods print one warning on first use. Removal comes after a release where the default is deny, so the exact version is not fixed yet.
8.2. How do we get rid of the warning “A terminally deprecated method in sun.misc.Unsafe has been called”?
The real fix is to replace the call, or to upgrade the library that makes it, using the class named in the second warning line. As a short-term measure, –sun-misc-unsafe-memory-access=allow hides the warning on Java 24 to 27.
8.3. Is VarHandle as fast as Unsafe?
For the same access mode, yes in practice. The JIT compiler turns a call on a static final VarHandle into the same instructions as the Unsafe call, and performance on par with Unsafe was a stated goal of JEP 193. The handle must be stored in a static final field, otherwise the compiler cannot optimize it.
8.4. What is the difference between sun.misc.Unsafe and jdk.internal.misc.Unsafe?
The class sun.misc.Unsafe is the old public-by-accident API in jdk.unsupported, kept for compatibility. The class jdk.internal.misc.Unsafe is the JDK’s own implementation, in a package that the module java.base does not export, so application code cannot use it without –add-exports java.base/jdk.internal.misc=ALL-UNNAMED.
9. Conclusion
The class sun.misc.Unsafe gave libraries raw memory access and CAS before the JDK had safe APIs for them. On Java 25, almost all of its methods are deprecated for removal, the JVM warns on first use, and a future release makes the memory-access methods throw.
The migration path is clear. Field and array access move to VarHandle or the atomic classes, off-heap memory moves to Arena and MemorySegment, and the old tricks have plain Java versions. Running tests with –sun-misc-unsafe-memory-access=deny shows what still needs work. The Java examples page groups more small programs by task.
10. References
- JEP 471, Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal
- JEP 498, Warn upon Use of Memory-Access Methods in sun.misc.Unsafe
- JEP 454, Foreign Function and Memory API
- JEP 193, Variable Handles
- VarHandle Javadoc (Java 25)
- MemorySegment Javadoc (Java 25)
Happy Learning !!
Of course, much better to never store a password in a String object. This is why for example JPasswordField gives you a char[] rather than a String:
https://docs.oracle.com/javase/tutorial/uiswing/components/passwordfield.html
True
Lokesh,
First, thanks for a great post.
In your example, Player p = (Player) unsafe.allocateInstance(Player.class); Where is the object created? On JAVA HEAP or non-heap direct memory?
Are all the operations you have explained in the article non-heap allocations? I ask this because, when you use “new” key-word, its supposed to create an instance on the heap. If it does, then what is the actual advantage, because then, it doesnt by-pass the GC.
Thanks
Ashley
Can I convert this to byte[]? I want to allocate some bytes (128*1024*1024) using SuperArray, but I can’t write this to file (without split array)
great work sir ji..Really help full.