This post discusses preview features in JDK 28
JEP 401, a major Valhalla milestone, has been integrated as a preview feature in JDK 28. This is very exciting, as value classes increase both our ability to communicate the semantics of our programs to others and the optimization opportunities available to the JVM.
However, I have seen people online essentially taking a “Value all the classes!” approach to this. I worry that there is a belief that ordinary classes give you a floor on performance, and that value classes will do their best to raise you above that floor, but won’t ever take you below it. Unfortunately, that is not true. A well-intentioned program may put the JVM into a situation where a flattened representation is faster for some methods, and a reference representation is faster for others. When these methods interact, the JVM is forced to convert between the two representations. I want value classes to be more than just magic, so today I am going to show you what the JVM is capable of right now, and where its limitations are. I hope that with this you’ll have some context for reasoning about the code that you (or your AI agent) write.
The main optimization advantage of value classes is that we give up identity. This gives the JVM freedom to choose a suitable representation for a particular situation. Without the requirement of identity, the runtime can more readily flatten values (avoiding pointer chasing), scalarize them by representing their components independently in registers or on the stack. For the value object itself, escape analysis becomes trivial: there is no identity whose escape must be proven unobservable.
We are going to examine three examples: a large final value stored flat, a direct value transformation compiled without allocation, and a generic virtual call that requires materialization.
Immutability enables flattening
JEP 539, Strict Field Initialization in the JVM, lets the JVM rely on a final field having been initialized before its enclosing object becomes observable. Because such a field cannot later be updated, the JVM may use a non-atomic flattened layout without risking a torn assignment. Mutable fields, however, must preserve tear-free assignment. If a mutable field contains a value that is too large for an atomic flattened update, the JVM must instead use a reference layout. The strict-initialization guarantee opens up many optimization possibilities.
Consider this small example:
value record FourLongs ( long a, long b, long c, long d) {} record Envelope (FourLongs payload) {}
FourLongs has 32 bytes of payload, making it too large for an atomic flattened update in the current JVM. But Envelope.payload is a record component and therefore a strictly initialized final field: once initialized, it is never updated. The JVM is consequently free to store payload using a non-atomic flattened layout. In the current Valhalla master build, the field-layout diagnostic reports the following when using PrintFieldLayout :
... continue reading