SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: JDK 27 turns on compact object headers and makes G1 the default everywhere

· by Serguey Asael Shinder / Serguey Shinder

Oracle's Per-Ake Minborg and Claes Redestad published their performance round-up for JDK 27 on Inside Java on 28 September. More than 2,300 commits landed in OpenJDK since JDK 26. Two changes affect almost every application without a line of code changing, and both have a flag to switch them back.

Compact object headers are on by default. JEP 534 enables them in JDK 27, after they were experimental in JDK 24 and a product feature in JDK 25. On a typical 64-bit HotSpot configuration every object header shrinks from 12 bytes to 8. The post cites the measurements from JEP 519, which made the feature production-ready: 22% lower heap use and 8% lower CPU use in one SPECjbb2015 configuration, 15% fewer collections in another, and about 10% lower runtime in a parallel JSON parser benchmark. Heaps full of small objects gain most; heaps dominated by large primitive arrays gain least. The way back is -XX:-UseCompactObjectHeaders, which the authors recommend for an A/B comparison during the upgrade and as a fallback if a low-level tool or agent assumes the old object layout.

G1 is the default garbage collector everywhere. HotSpot already picked G1 on server-class machines, but could still choose Serial GC in small or constrained environments. JEP 523 removes that exception. The authors call it a consistency change, not a claim that G1 suits every workload, and point out who will notice: small containers, single-processor environments and development machines that used to fall outside the server-class heuristic. Pause times, CPU use and memory overhead can change even if the application does not. -XX:+UseSerialGC brings Serial back.

Serguey Asael Shinder: JDK 27 turns on compact object headers and makes G1 the default everywhere
JDK 27 turns on compact object headers and makes G1 the default everywhere — Serguey Asael Shinder

One library change you might measure directly. HashMap.putAll() and the HashMap(Map) constructor used to copy through the Map interface, with an iterator and calls to getKey(), getValue() and hashCode() that become megamorphic when a call site sees several map types. JDK-8371656 adds direct table walks when the source is exactly a HashMap, or an unmodifiable map backed by one, and reuses the stored hashes. On AWS Graviton, with call sites exposed to five map types, the reported time per operation fell by 61% to 86%; for an unmodifiable 150-entry map, from about 10,593 to 1,533 ns.

The honest caveat. The post says its numbers are the local effect of each change, not whole-application results, and that minimal applications start in roughly the same time, or slightly more slowly, on JDK 27 than on JDK 26, while using up to 1 MB less memory.

What to do before upgrading. Run your heap and GC dashboards once with the new defaults and once with -XX:-UseCompactObjectHeaders, and if you run in small containers, once with -XX:+UseSerialGC. The defaults are a better starting point for most services, but "default" is a guess about your workload, and the flags exist so you can check it.

No JDK runs on the machine this note was written on; the figures are those published in the linked post and pull request.