Part 1 followed method calls through the stack and its frames. This part turns to the heap and asks what a single object really costs: its header, its padding, and what Java 25’s compact headers take away.
Object Headers - The Hidden Cost
The Header Tax
Create a simple object:
class Point {
int x;
int y;
}Expected: 8 bytes
Traditional JVM: 24 bytes
Every object carries 12 bytes of header metadata:
- Mark word8 bytesHash, lock state, GC age
- Class pointer4 bytesReference to class metadata
Every object is also padded to a multiple of 8 bytes. The fields of Point
start right after the header, at offset 12, and 4 bytes of padding go at the
end of the object. The combined header plus padding costs
2× the actual data for small objects.
Java 25 changes the math. By embedding a 22-bit class index into unused Mark Word bits, the JVM compresses the header from 12 bytes to 8 bytes:
Traditional24 bytes
Compact (Java 25)16 bytes, 33% less
Arrays also carry a 4-byte length field, and their size is rounded up to
8 bytes as well, so the saving often disappears: int[10] is 56 bytes in both
cases - compact headers mainly benefit small objects without array overhead.
Enable with: -XX:+UseCompactObjectHeaders
Seeing It In Action
Let’s use Java Object Layout (JOL) to inspect actual object sizes:
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class ObjectHeaderDemo {
public static void main(String[] args) {
System.out.println(VM.current().details());
// Simple object with two int fields
class Point {
int x;
int y;
}
Point p = new Point();
System.out.println("=== Point Object Layout ===");
System.out.println(ClassLayout.parseInstance(p).toPrintable());
// Array header with length field
int[] intArray = new int[10];
System.out.println("\n=== int[10] Array Layout ===");
System.out.println(ClassLayout.parseInstance(intArray).toPrintable());
// Demonstrate hash code storage
Object o = new Object();
System.out.println("\n=== Before hashCode() ===");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
int hash = o.hashCode();
System.out.println("\n=== After hashCode(): " + hash + " ===");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}Expected output analysis:
- Point object: 12 bytes header + 8 bytes fields + 4 bytes alignment padding at the end = 24 bytes (traditional), 16 bytes (compact)
- Bare Object: 12 bytes header + 4 bytes padding = 16 bytes (traditional), 8 bytes (compact) - the most dramatic single-object reduction
- int[10] array: 12 bytes header + 4 bytes length + 40 bytes data = 56 bytes (already 8-byte aligned, no extra padding needed)
- Hash code: Notice how the Mark Word changes after
hashCode()is called - the hash is computed once and cached in the header
The Mark Word: A Multi-Purpose Chameleon
The 64-bit Mark Word is remarkably versatile. It stores:
- Identity hash code (31 bits): Computed lazily on first
hashCode()call - GC age (4 bits): Number of GC cycles survived (max 15 before tenuring)
- Lock state (3 bits): Unlocked, lightweight locked, heavyweight locked, marked for GC
The same 64 bits serve different purposes depending on the object’s state.
When you call hashCode(), bits that were unused suddenly store your hash.
When you synchronized(obj), the bits transform to hold lock information.
During GC, they become forwarding pointers.
Key Takeaway: Object headers (12 bytes) plus alignment padding create 16 bytes of overhead minimum - often consuming 50-67% of memory for small objects. In applications creating millions of small objects (microservices, caches, JSON parsing), this overhead compounds into gigabytes of wasted RAM.
Java 25 - The Latest Chapter
Compact Object Headers (JEP 519)
Java 25 introduces Compact Object Headers as a production-ready feature (experimental in Java 24). The innovation is elegant: compress the 12-byte header down to 8 bytes.
How it works: The JVM overlays the class pointer onto unused bits in the Mark Word. Traditional headers have a separate Mark Word (64 bits) and Class Pointer (32 bits). Compact headers embed a 22-bit class index into the Mark Word itself.
Why 22 bits? Because 2²² = 4,194,304 - enough to address over 4 million unique classes. Even the largest applications rarely exceed this.
Project Lilliput: This feature represents the first deliverable from Project Lilliput, OpenJDK’s initiative to minimize Java object memory overhead. The project roadmap targets 4-byte headers in a future release - halving the size once again. For now, the 12→8 byte reduction is just the beginning.
The Math:
Traditional96 bits = 12 bytes
Compact (Java 25)64 bits = 8 bytes
33% reduction (12→8 bytes).
Note that the percentage savings vary by object size: a bare Object with no
fields shrinks from 16 bytes (12 header + 4 padding) to 8 bytes - a 50% reduction.
Objects with fields see proportionally less dramatic savings as the data
payload dominates.
Real-World Impact
JEP 519 reports these measurements, and Compact Object Headers have been tested at Amazon by hundreds of services in production:
- Heap usage22% lessSPECjbb2015
- CPU time8% lessSPECjbb2015
- Garbage collections15% fewerSPECjbb2015, G1 and Parallel
- JSON parsing10% less timeParallel parser benchmark
The gains are most dramatic for applications with many small objects:
- Spring Boot microservices (thousands of small objects per request)
- In-memory caches (Caffeine, Ehcache)
- JSON/Message processing pipelines
Applications with few, large objects (video processing, image manipulation) see less benefit - the header was already negligible compared to data payload.
Enabling Compact Headers
Just add one JVM flag:
# Java 24 (experimental - unlock needed)
java -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders -jar myapp.jar
# Java 25 (production-ready)
java -XX:+UseCompactObjectHeaders -jar myapp.jar
# Verify it's working
java -XX:+UseCompactObjectHeaders -XX:+PrintFlagsFinal 2>&1 | grep UseCompactObjectHeadersThat’s it. No code changes. No recompilation. No regression testing.
Key Takeaway: Java 25 delivers 33% reduction in object header overhead with a single flag. It’s the most memory-efficient Java release ever - without changing a line of application code.
Summary
- Object Headers: 12 bytes (Mark Word + Class Pointer), with alignment padding to a multiple of 8 bytes at the end of the object
- Java 25: Compact Object Headers reduce overhead from 12 to 8 bytes (33% reduction)
Once you internalize these fundamentals, you start seeing hidden costs everywhere. A cache with a million entries pays 12MB in header overhead before storing a single byte of data. A microservice processing thousands of requests per second allocates more bytes for headers than for the actual request payload.
This overhead isn’t inevitable. Java 25 cuts it by a third with a single flag - no code changes, no recompilation, no testing cycles. The JVM spent thirty years optimizing garbage collection down to millisecond pauses. Now it’s optimizing the objects themselves.
Add -XX:+UseCompactObjectHeaders and measure the difference.
References
-
JEP 519: Compact Object Headers
- Kennke, Roman. “JEP 519: Compact Object Headers.” OpenJDK, 2025.
- https://openjdk.org/jeps/519
-
Java Object Headers Deep Dive
- Xesquevixos, Wanderson. “Mastering Memory Efficiency with Compact Object Headers in JDK 25.” JAVAPRO International, February 2026.
- https://javapro.io/2026/02/10/mastering-memory-efficiency-with-compact-object-headers-in-jdk-25/
-
Java 25 Compact Headers Integration
- InfoQ, June 2025.
- https://www.infoq.com/news/2025/06/java-25-compact-object-headers/
-
Understanding Object Headers
- Shamaii, Kiarash. “Understanding Object Header in Java.” Medium, September 2025.
- https://medium.com/@kiarash.shamaii/understanding-object-header-in-java-175c42329b90
-
Java Object Layout (JOL)
- Shipilev, Aleksey. “Java Object Layout.” OpenJDK Project.
- http://openjdk.java.net/projects/code-tools/jol/