Compact Object Headers in Java 25
Compact object headers are a JVM feature that shrinks every object's header from 12 bytes to 8 bytes on 64-bit systems. It was added as an experiment in Java 24 and became a supported product feature in Java 25 (JEP 519), turned on with -XX:+UseCompactObjectHeaders. Because most programs are full of small objects, saving 4 bytes per object can cut heap use noticeably. In this lesson, you will learn how compact headers work and measure the difference.
How It Works
The default header has an 8-byte mark word and a separate 4-byte class pointer. Compact headers squeeze a shorter class pointer into the mark word, so the whole header fits in 8 bytes. Whether an object actually gets smaller depends on padding: the saving shows when the 4 bytes cross an 8-byte boundary.
Example: Measuring the Saving
Let's see an example that measures object sizes with the JVM's class histogram. It is run twice: once normally and once with compact headers.
import java.lang.management.ManagementFactory;
import javax.management.ObjectName;
public class HeaderSizes {
static class Empty { }
record Point(int x, int y) { }
record Student(String name, int marks, boolean passed) { }
static long bytesEach(String className) throws Exception {
String histogram = (String) ManagementFactory.getPlatformMBeanServer()
.invoke(new ObjectName("com.sun.management:type=DiagnosticCommand"),
"gcClassHistogram", new Object[] {null},
new String[] {String[].class.getName()});
for (String line : histogram.split("\n")) {
String[] p = line.trim().split("\\s+");
if (p.length >= 4 && p[3].equals(className)) {
return Long.parseLong(p[2]) / Long.parseLong(p[1]);
}
}
return -1;
}
public static void main(String[] args) throws Exception {
Object[] keep = new Object[300_000];
for (int i = 0; i < 100_000; i++) {
keep[i] = new Empty();
keep[100_000 + i] = new Point(i, i);
keep[200_000 + i] = new Student("Navika", i, true);
}
System.out.println("Empty: " + bytesEach("HeaderSizes$Empty"));
System.out.println("Point: " + bytesEach("HeaderSizes$Point"));
System.out.println("Student: " + bytesEach("HeaderSizes$Student"));
System.out.println(keep.length + " objects kept");
}
}java HeaderSizes.java
java -XX:+UseCompactObjectHeaders HeaderSizes.javaEmpty: 16
Point: 24
Student: 24
300000 objects kept
Empty: 8
Point: 16
Student: 24
300000 objects keptExplanation: With compact headers, an empty object needs only its 8-byte header, and Point drops from 24 to 16 bytes (8 + 8, no padding), a 33% saving. Student stays at 24: 8 + 9 bytes of fields = 17, still padded to 24. Programs with many small objects, such as nodes, records and boxed values, gain the most.
Should You Turn It On?
- Benefits: less heap use, fewer garbage collections, and often better CPU cache use.
- Status: a supported product option in Java 25, off by default.
- Test first: measure memory and speed with your real workload before switching production systems.
Common Mistakes
Expecting every object to shrink by 4 bytes. Padding can absorb the saving, as with Student.
Assuming it's on by default in Java 25. It must be enabled with the flag.
Practice
- Easy: Predict the size of a record with one
intfield, with and without compact headers, then measure it. - Harder: Build a linked list of one million nodes, measure heap use with
MemoryMXBeanafterSystem.gc(), and compare with and without compact headers.
Key Takeaways
Was this tutorial helpful?