What’s wrong with Java’s sun.misc.Unsafe?


Java’s sun.misc.Unsafe class has been in use since 2002. It provides essential low-level methods that framework developers use to deliver otherwise unobtainable features and performance. Unfortunately, Unsafe also has long-standing problems related to JVM maintainability. And, as the name implies, it isn’t exactly safe to use. A newer JEP proposes to remove sun.misc.Unsafe’s memory access methods in a future Java release. But what will replace them?

This article looks at the Unsafe class, why some of its methods are slated for removal, and what developers can do to prepare for this change.

Why Java’s Unsafe methods are being deprecated

Java’s sun.misc.Unsafe class does some special things that are otherwise not allowed. Its powers fall into two general categories:

  • Low-level, pointer-like memory access and allocation
  • Class construction outside of normal means (even beyond reflection)

In exercising these powers, some methods of the Unsafe class break standard protections and memory management built into the JVM. Because of this, JVM implementations vary in how they accommodate Unsafe. The code is more brittle as a result, and may not be portable across JVM versions or operating systems. Having it in use also hinders developers’ ability to evolve JVM internals.

Why developers use Unsafe

Unsafe isn’t all bad, which is why it’s been maintained for so long. Baeldung’s Guide to sun.misc.Unsafe includes an overview of what developers can do with the Unsafe class:

  • Class instantiation without constructors
  • Direct manipulation of class fields
  • Throwing checked exceptions when they are not handled by the scope
  • Direct access to heap memory operations
  • Access to the compare and swap (CAS) operations

Java’s compare-and-swap operation is a good example of why we have an Unsafe class. CAS is an instruction at the hardware level, allowing atomic memory access. Atomicity gives significant performance benefits to concurrent threads because it allows us to modify memory without blocking. Traditionally, standard Java APIs couldn’t leverage this feature because it is specific to the operating system.

Here’s a snippet of a compare-and-swap operation using Unsafe, from Oracle’s introduction to sun.misc.Unsafe:


public final class AtomicCounter implements Counter {
    private static final Unsafe unsafe;
    private static final long valueOffset;

    private volatile int value = 0;

    static {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe"); // (1)
            f.setAccessible(true); // (2)
            unsafe = (Unsafe) f.get(null); // (3)
            valueOffset = unsafe.objectFieldOffset
                (AtomicCounter.class.getDeclaredField("value")); // (4)
        } catch (Exception ex) { throw new Error(ex); }
    }

    @Override
    public int increment() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1; // (5)
    }

    @Override
    public int get() {
        return value;
    }
}

You’ll notice even the access to Unsafe is strange. It’s a static class member that must first be accessed with reflection (1), then manually set to accessible (2), and then referenced, again with reflection (3).

With the Unsafe class in hand, we get the offset of the value field on the present AtomicCounter class (4). The offset tells us where in memory the field resides in relation to the class memory allocation. Note that here we’re dealing with pointers, which may be unfamiliar to Java developers, although they’re standard fare in direct memory management languages like C/C++. Pointers give us direct access to heap memory by putting the onus of memory access on the developer—something Java’s garbage collection mechanism was explicitly designed to avoid.

To execute the CAS instruction, the getAndAddInt() function is called (5). This tells the operating system to execute the atomic instruction, using this as the frame of reference, on the valueOffset location, and with the “delta” of 1—meaning the operating system should increment by 1.

Refactoring sun.misc.Unsafe

Now you’ve had a taste of using Unsafe. The example also highlights some of its drawbacks.

Performing incorrect memory operations can cause the JVM to “hard crash” without throwing a platform exception. Poor memory management can also lead to memory leaks, which are notoriously easy to create and hard to diagnose and fix in some languages. Direct memory access can also open up security holes like buffer overflows. (See Stack Overflow for a further review of the drawbacks of using sun.misc.Unsafe.)

But programmer-created bugs are only part of the problem. Using Unsafe also results in implementation-specific code. That means the programs using them may not be as portable. It also makes it more difficult for a JVM to change its implementations in those areas.

For all these reasons, Java’s developers are moving to introduce a platform-ordained way to perform the actions currently associated with the Unsafe class. The problem is that code using sun.misc.Unsafe is all over the place. It hits various frameworks at critical points. And because Unsafe deals with touchy low-level operations, the code is especially dicey to refactor.

Alternatives to sun.misc.Unsafe

Java’s developers are currently in the process of replacing Unsafe features with standard, less problematic versions. Let’s look at the alternatives, some of which were introduced in Java 9.

VarHandles

Variable Handles is described in JEP 193. This feature covers one of the biggest uses of Unsafe, which is directly accessing and manipulating fields on the heap. It’s worth reading the JEP’s goals to understand what this feature does, but here’s a summary:

  • It must not be possible to place the JVM in a corrupt memory state.
  • Access to a field of an object follows the same access rules as with getfield and putfield byte codes in addition to the constraint that a final field of an object cannot be updated.
  • Its performance characteristics must be the same as or similar to the equivalent sun.misc.Unsafe operations.
  • The API must be better than the sun.misc.Unsafe API.

In general, VarHandles gives us a safer, better version of Unsafe’s features, and it doesn’t compromise performance. You can see the range of operation types this feature supports by looking at the Javadoc for AccessModes. You’ll notice compare-and-exchange is supported, along with many other primitive operation types. 

Using VarHandle instances is a clean, safe way to perform many low-level operations directly on fields, without running the risks we do with Unsafe.

MethodHandles

The MethodHandles class (JEP 274) holds a Lookup class that replaces the use of reflection to obtain and alter permissions on fields (the technique you saw earlier in the CAS example). With this class, permissions are granted according to the access of the containing code instead of being set directly with reflection.

The Stack-Walking API

The original Unsafe.getCallerClass() is partially replaced with the Stack-Walking API delivered in JEP 259. It provides a safer albeit more roundabout way to access the caller class.

Solutions still needed

Some outstanding Unsafe features remain to be replaced. An important example is the capacity for creating proxies and mocks. (Ben Evans provides a good overview of the issue in his Java Magazine article, Unsafe at any speed.)

It’s also interesting to look at how real-world projects are wrestling with this issue. For example, consider how the Objenesis Git project dealt with Unsafe.defineClass() being dropped from the API.

Based on this example, we can see that there is still work to be done to fully replace Unsafe‘s capabilities in creating classes without constructors. 

Conclusion

The brass ring for Java’s Unsafe journey is to completely replace all the memory-access features of Unsafe with authorized versions. As you can see, this work isn’t happening all in one place. Bits and pieces will be added through newer APIs, although VarHandle does address a big swath of the work.

Having new versions of the functionality is only half the battle. The various frameworks and projects that currently rely on Unsafe methods must migrate to the new options, which is in no way simple. In some cases, it will require serious refactoring.

Gradually, the JVM will probably continue to deprecate and remove the Unsafe features as valid alternatives solidify. It appears that Java’s stewards are committed to responsibly patching this long-standing area of the platform while ensuring the stability of the ecosystem that depends on it.

Copyright © 2024 IDG Communications, Inc.



Source link