Juggling chainsaws: Unsafe handling of Value objects

If you’ve been writing Unsafe code before Java 28, it’s probably time to stop. And to migrate to something easier to use correctly, like Varhandles or FFM.

JEP 401 introduced value objects as a preview feature into Java. Value objects are a great example of a “give something up, get something better” tradeoff. You give up identity (which also means giving up mutability), and get better memory density on the stack through scalarization and in the heap for fields and arrays through flattening.

This is a great win in general for Java. The JVM does all the complex work of figuring out better layouts for fields to support flattening. And is continuing to develop more places to flatten based on “seeing through” sealed hierarchies, or using the strict initialization and lack of mutability of “strict final” instance fields to flatten layouts that are larger than the atomic access requirements would otherwise support.

As the JVM wields its freedom to optimize layouts in more ways, those who have historically been successful juggling chainsaws - err rather Unsafe - will find themselves picking their appendages up off the ground.

Ok, enough with the slightly gross analogy.

Historic Unsafe access

Prior to JEP401, Unsafe only had to worry about a fixed number of layouts - the 8 primitives and a single reference type:

  • boolean
  • byte
  • short
  • char
  • int
  • long
  • float
  • double
  • Object (reference)

and the equivalent array types for each case. The case that changes for value objects is the object (reference) case. The historic pattern has been:

Unsafe U = ...
Field someField = Foo.class.getDeclaredField("someField");
long offset = U.objectFieldOffset(someField);
U.getReference(aFoo, offset);

While for arrays, the typical pattern has been:

Unsafe U = ...
long array_base = U.arrayBaseOffset(Object[].class);
long scale = U.arrayIndexScale(Object[].class);
Object o = U.getReference(array, array_base + (scale * index));

with a lot of code assuming, due to array covariance, that the base and scale for Object[] were applicable to all reference arrays.

The old access patterns - and the assumptions they are based on - no longer hold.

Value object Unsafe access

Object layouts are more complicated for value types. The fields may be flattened (or not), they may have additional alignment constraints, or extra state (like the null marker), or … the options grow as we add more optimizations. And there is no way to reason about the layout from the class files - there are too many factors that can’t be determined statically without duplicating JVM’s entire layout algorithm.

The best practice is to treat the layout as an opaque piece of information. Trying to manually decode the layout info is a losing game - it will change from run to run, across heap sizes, due to JVM command line flags, with class hierarchy changes, and as we add more optimizations.

There is only one meaningful question to ask of a layout - is it a non-flat layout. This can be done by comparing the layout token returned by Unsafe.fieldLayout(Field f) for fields, or Unsafe.arrayLayout(Object[] arr) for array instances, to the Unsafe.NON_FLAT_LAYOUT constant. While field layout tokens can be cached, the array layout tokens must be fetched for each array instance.

This makes the Unsafe code you write more complicated as there are now more degrees of freedom affecting layouts which must be taken into account when accessing fields and array elements.

Rather than documenting the (current) forms to use for Unsafe access, I’d rather again point everyone to VarHandles (for on heap) and FFM (off heap) instead. These APIs are maintained by the JDK team and will always do the right thing for access.

And none of this mentions the risk of messing up the access protocols for value objects. “I only want to change one field” or “I just want to modify the null marker” or any other bit of hacky code. It’ll work just enough to pass testing before failing at just the wrong time. Best to avoid it.

Takeaway

Valhalla enables more optimized and more complicated object layouts. Writing correct Unsafe code has become more complex. Don’t do it. Use safer alternatives like VarHandle or FFM.

Written on August 26, 2026