Every single object you allocate on the JVM heap carries a header before any of its actual fields — identity hash code, GC bookkeeping, lock state, a pointer to its class. That header has been 96 bits since compressed oops became the norm. JEP 534 shrinks it to 64 bits, and as of JDK 27, that shrinkage is on by default instead of something you had to know to ask for.
Why the header size matters at all #
96 bits doesn’t sound like much until you remember it’s paid by
every object, not just big ones. A Long wrapper, a linked-list
node, an Optional — for small objects the header can be a third or
more of the object’s total footprint. Heap-dense workloads (lots of
small, short-lived objects — which describes a large share of typical
Java services) pay that tax proportionally harder than anything else.
Smaller headers mean more objects fit in cache lines and in the heap
itself, which is where the “increasing data locality” part of the
JEP’s stated goal actually comes from — it’s not just about bytes
saved, it’s about how much more of the heap fits where the CPU can
reach it quickly.
History #
Below is the history of the feature
| JDK | JEP | Status |
|---|---|---|
| 24 | JEP 450 | Introduced as an experimental, opt-in feature |
| 25 | JEP 519 | Promoted to a full product feature — still opt-in |
| 27 | JEP 534 | Default on — the old 96-bit layout is still available, just no longer the default |
Straight from the JEP’s own motivation section:
That’s a meaningfully different claim than “we benchmarked this in a lab.” Two large, separate organizations ran this in production, on their own backports, before the JDK project itself turned it on by default.
What it actually buys you #
Numbers from the JEP, unmodified:
- SPECjbb2015 uses 22% less heap space and 8% less CPU time
- Garbage collections during that same SPECjbb2015 run drop by 15% (holds for both G1 and Parallel)
- A highly parallel JSON parser benchmark runs 10% faster
None of that requires touching application code. It’s a JVM flag default flip, not an API.
Flipping it off, if you need to #
The flag itself hasn’t changed — only its default value has, which means the meaning of not passing it flipped between JDK 25 and JDK 27:
# JDK 24-25: had to opt in explicitly
java -XX:+UseCompactObjectHeaders -jar app.jar
# JDK 27+: on by default; opt out explicitly if you need to
java -XX:-UseCompactObjectHeaders -jar app.jar-XX:+UseCompactObjectHeaders forward from a JDK 24/25 upgrade, it’s
harmless on JDK 27 — you’re just being explicit about a value that’s
now already the default. But if you have monitoring or tooling that
reads raw heap dumps or does anything with object layout assumptions,
that’s the thing actually worth testing before this upgrade, not the
flag itself.
What happens if you need the space back later #
The JEP reserves four bits of the new 64-bit header up front for Project Valhalla (value types), which is a reasonable thing to want to know before you go build something that assumes every remaining bit is free forever. Its own risk section is short and direct about the headroom that’s left:
“If a need for even more bits arises, we can shrink compressed class pointers and identity hash codes even further, using techniques we have already prototyped in Project Lilliput.”
And notably: removing the old 96-bit layout entirely is explicitly not a goal of this JEP. If compact headers ever cause a problem for you specifically, the escape hatch stays available — it’s just not the path most people will take anymore.