Compact Object Headers Feature ImageCompact Object Headers Feature Image
HappyCoders Glasses

Java Compact Object Headers
(JEP 450)

Sven Woltmann
Sven Woltmann
Last update: April 7, 2025

Every Java object has an object header that precedes the actual data in memory. The header mainly contains the hash code of the object and the information on which class the object is an instance of.

As of Java 24, the object header is 96 bits (12 bytes) in size – or 128 bits (16 bytes) if Compressed Class Pointers are switched off (although there is almost no reason to do so).

As part of Project Lilliput, the JDK developers have been working for many years on ways to compress the header to a total of 64 bits or even 32 bits.

In Java 24, the time had come: JDK Enhancement Proposal 450 introduced so-called “Compact Object Headers” – for now in “experimental” status. Compact Object Headers make it possible to compress the object header from 96 bits to 64 bits and thus significantly reduce the heap size of existing applications.

In this article you will find out:

  • How does header compression work?
  • Why does this not only reduce memory requirements but also increase application performance?

Status Quo

You can find a detailed description of the structure of object headers in the main article on Java object headers. Here is a summary of the most essential points:

The object header usually consists of a 64-bit “Mark Word” and a 32-bit “Class Word”. Mark Word and Class Word are structured as follows:

JEP 450: Java Object Header with Mark Word and Class Word

The Mark Word contains:

  • a 31-bit identity hash code (which is returned by the System.identityHashCode(Object) method),
  • 4 bits in which the garbage collector stores the age of an object (based on which it decides when to move an object from the young to the old generation),
  • 2 “tag bits” that indicate whether the object is not locked, locked uncontended (without waiting threads), or locked contended (with waiting threads).

When using the outdated Legacy Stack Locking, the first 62 bits of the mark word in the locked state were replaced by a pointer to a lock data structure. Since Java 23, so-called Lightweight Locking has replaced this outdated mechanism.

The old mode can still be reactivated using the VM option -XX:LockingMode=1; however, it cannot be combined with Compact Object Headers.

The Class Word contains a 32-bit offset in the maximum 4 GB compressed class space to a so-called class data structure containing all relevant data about the object’s class.

If Compressed Class Pointers have been deactivated with -XX:-UseCompressedClassPointers, then the Class Word is 64 bits in size and contains an uncompressed pointer. The deactivation of Compressed Class Pointers cannot be combined with Compact Object Headers either.

From Compressed Class Pointer to Compact Object Header

How can we further compress the object header based on Compressed Class Pointers?

First of all, the Mark Word (as you can see above) currently contains 27 unused bits (25 at the beginning and one each before and after the “age bits”). That means that of the 96 bits of the entire object header, only 96 - 27 = 69 bits are required. To get to 64 bits, we must somehow save five bits.

Where can we get them?

The JDK developers experimented for a long time until they came up with the following solution (I changed the scale for better visualization – the 64 bits now cover the entire width):

JEP 450: Compact Object Header

The new 64-bit header is no longer divided into Mark Word and Class Word but contains the following information directly:

  • a class pointer further compressed from 32 bits to 22 bits (explained below),
  • the 31-bit Identity Hash Code (unchanged),
  • 4 bits reserved for Project Valhalla (new),
  • 4 bits for the age of the object (unchanged),
  • 1 bit for the so-called “Self Forwarded Tag” (explained below),
  • 2 Tag bits (unchanged).

The class pointer has, therefore, been reduced by 10 bits. As we only had to save five bits, five additional bits are now available. Four of these were reserved for Project Valhalla, and the new “Self Forwarded Tag” is stored in one bit.

How Could Class Pointers Be Compressed to 22 Bits?

With the previous 32 bits, we could individually address each position within the 4 GB compressed class space.

For the sake of simplicity, I will illustrate this in the following image with a 256-byte memory area:

256 byte memory area

As you can see, we need the numbers 0 to 255 to address each position of the memory area. To do this, we need an 8-bit pointer (28 = 256).

But do we really need to be able to address every single position? No, we don't!

Just as a hard disk (whether a conventional one or an SSD) is divided into so-called blocks (usually 4 KB in size), we can also divide the memory area for the class data into blocks. This means we no longer have to address each individual byte but only each block. And so we can address the same memory area with significantly fewer bits.

Here again is the simplified example, in which I divided the 256-byte memory area into 32 blocks of 8 bytes each:

256 byte memory area divided into 8 byte blocks

Now, we only need the numbers 0 to 31 to address the same memory area. Therefore, we only need 5-bit pointers (25 = 32). By dividing into blocks, we reduced the memory requirement per pointer from 8 bits to 5 bits.

This also works with the memory area in which the class information is stored.

When using Compact Object Headers, this memory area is divided into 1,024 (=210) byte blocks. The JDK developers chose this value because most classes occupy between half a kilobyte and one kilobyte.

As a reminder, the area is 4 GB in size. This results in 4 × 1,024 × 1,024 × 1,024 / 1,024 blocks, i.e. 4 × 1,024 × 1,024, which is 4,194,304, or 222 blocks. And we can address these with 22 bits!

To turn a 22-bit block number into a pointer, we only have to shift the 22 bits to the left by 10 bits and fill the last 10 bits with zeros, and we have a 32-bit pointer into the 4 GB memory area again:

22-bit block number becomes a 32-bit class pointer

The division into blocks now leads to class data fragmentation. However, the JDK developers have also considered this: the memory between the classes can also be used by other data structures in the metaspace.

What Is the “Self Forwarded Tag”?

When a garbage collector copies an object to a new memory address, it replaces the upper 62 bits of the mark word in the original object with a pointer to the new memory address and sets the tag bits to 0x11. It then finds the original mark word at the new address.

If the copy operation fails, the mark word is replaced by a pointer to the object itself. As a result, the identity hash code and object age are lost, but this seems to be bearable (unfortunately, I could not find any reliable information about why this is the case, but I will update this section if I find a statement on this).

However, if we were to replace a compact object header with a self-reference, the class pointer would also be lost. As this pointer is essential, a compact object header must never be replaced by such a self-reference.

Instead, the new “Self Forwarded Tag” bit is set.

Conclusion on Compact Object Headers

Compact Object Headers significantly reduce the memory requirements of a Java program by reducing the object headers from 96 bits (12 bytes) to 64 bits (8 bytes).

Not only that: because the objects are smaller, more objects fit into the CPU cache. This results in fewer cache misses – and this also has a positive effect on performance.

Compact Object Headers are still in the experimental stage and must be activated with the following VM option:

-XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders

Have you already tested Compact Object Headers? Did it bring the expected improvements? Share your experience in the comments!

Would you like to be informed as soon as Compact Object Headers are finalized and ready for production? Then click here and sign up for the HappyCoders newsletter, in which I will keep you regularly informed about the latest developments from the Java world.