java object header and compressed class pointers hero imagejava object header and compressed class pointers hero image
HappyCoders Glasses

Java Object Headers and Compressed Class Pointers​

Sven Woltmann
Sven Woltmann
Last update: December 4, 2024

Every Java object in memory contains not only the actual data but also a so-called “Object Header” that precedes the data. This header includes, for example, the identity hash code of an object, information about the object’s age, and a reference to the class that was instantiated by this object.

In this article, you will learn:

  • How is the object header structured?
  • What are the Mark Word and Class Word?
  • What is the Compressed Class Space?
  • How can Compressed Class Pointers be represented on a 64-bit system using only 32 bits?

This article describes the structure of the object header as of Java 23, i.e. before it was further compressed to so-called “Compact Object Headers.” An experimental version of Compact Object Headers will be introduced in Java 24 as part of Project Lilliput.

Structure of the Java Object Header

The Java Object Header comprises a “Mark Word” and a “Class Word.” On a 64-bit system with uncompressed pointers, the header occupies 128 bits – i.e., 16 bytes – and has the following structure:

Java Object Header: 64-bit Mark Word and 64-bit Class Word, total: 128 bits

With compressed pointers (I’ll explain what that means in the Compressed Class Pointers section), the class word is only 32 bits long – and thus, the entire object header is no longer 128 bits, but only 96 bits – i.e., 12 bytes:

Java Object Header: 64-bit Mark Word and 32-bit compressed Class Word, total: 96 bits

What data do Mark Word and Class Word contain, and how are they structured?

Mark Word

First, let’s look at the Mark Word (note that I changed the scale from the previous graphics to show the details better):

Java Mark Word layout

The Mark Word typically contains the following information:

In the obsolete “Legacy Stack Locking,” the Mark Word could change.

Legacy Stack Locking

In “Legacy Stack Locking” (the standard locking mechanism until Java 22), when the object is locked (i.e. when a thread is within a synchronized block defined on this object), the first 62 bits of the Mark Word are replaced by a pointer to an additional data structure on the stack:

Java Mark Word in locked state

This data structure then contains the actual Mark Word as well as additional information about the lock, such as a list of threads that have been blocked and are waiting to be allowed to enter the synchronized block.

The pointer to this separate data structure is one of two reasons for pinning in virtual threads: A virtual thread that calls blocking code within a synchronized block must not be unmounted from the carrier thread.

If the virtual thread were later mounted on another carrier thread (whose stack would be located at a different address in memory), that pointer would no longer be valid.

Since “Legacy Stack Locking” made it difficult to access the actual data of the Mark Word and was one reason for the aforementioned pinning, it was replaced by the more modern “Lightweight Locking.”

Lightweight Locking

Java 21 introduces so-called “Lightweight Locking,” which operates without modifying the Mark Word. Lightweight Locking has been the default mode since Java 23.

With Lightweight Locking, if no other thread wants to enter the critical section, only the tag bits (the last two bits of the Mark Word) are changed from 0x01 (unlocked) to 0x00 (lightweight-locked). No additional data structure is created.

Only when another thread attempts to enter the critical section is the lock "inflated":

  • An additional data structure is created, which contains, among other things, a list of waiting threads.
  • The pointer to this data structure is stored in a separate hashtable and no longer in the Mark Word. The inflation of the lock is merely indicated there by changing the tag bits to 0x10.

Lightweight locking is, therefore, a more efficient way to synchronize threads by making the mark word modification obsolete and reducing the overhead of unnecessary monitor objects in scenarios without thread contention.

Class Word

The Class Word (sometimes also spelled “Klass Word”) is quickly explained:

It contains a pointer to the Class object that is returned for an object with getClass(). On a 64-bit system, this pointer is also 64 bits in size (unless it is compressed – we'll get to that in a moment):

It contains a pointer to the so-called Klass data structure in metaspace – a memory area outside the Java heap. This data structure contains all relevant information about the class of the object. All objects of the same class, e.g., all ArrayList objects, point to the same Klass data structure.

On a 64-bit system, this pointer (unless it is compressed – more on that later) is also 64 bits in size:

Java Class Word, uncompressed, 64 bits

With these 64 bits, we can address 16 EB (16 Exabytes = 18,446,744,073,709,551,616 bytes). A Klass data structure is usually between half a kilobyte and one kilobyte in size. With 64 bits, we could, therefore, reference 264 / 768 = 24 quadrillion classes. This is a number that is likely to seem very large even in 30 years.

Therefore, the so-called “Compressed Class Space” and “Compressed Class Pointers” were introduced, which I will describe in the following two sections.

Compressed Class Space

The “Compressed Class Space” is a contiguous memory block within the metaspace (a memory area outside the heap) where all Klass data structures are stored. This area is allocated when a Java program starts, and its size cannot change during runtime.

Java memory layout: Heap, Metaspace, Compressed Class Space, C Heap, Thread Stack

By default, the Compressed Class Space is 1 GB in size. You can change its size with the following VM option:

-XX:CompressedClassSpaceSize=<size>

Allowed values are between 1 MB and 4 GB.

The name “Compressed Class Space” is misleading because it is not the Klass data structures themselves that are compressed but rather the pointers from the Class Word of the object header to these Klass data structures. More on that in the next section.

Compressed Class Pointers

As mentioned in the previous section, the Compressed Class Space can be a maximum of 4 GB in size. To address 4 GB, 32 bits (232 = 4,294,967,296) are sufficient.

A Compressed Class Pointer is, therefore, a 32-bit value that defines the address of the Klass data structure as an offset within the Compressed Class Space:

Java Class Word, 32 bits, with Compressed Class Pointer

The actual address of the Klass data structure is obtained by adding the starting address of the Compressed Class Space and this offset.

Compressed Class Pointers Are Enabled by Default

On a 64-bit system, Compressed Class Pointers are enabled by default. You can disable them with the following option:

-XX:-UseCompressedClassPointers

However, there is no reason to do this unless you suspect a bug in the implementation of compressed class pointers as the cause of a problem in your application.

Until Java 14, the activation of Compressed Class Pointers was coupled to the activation of Compressed OOPs (compressed object pointers). If you disabled Compressed OOPs, Compressed Class Pointers were also automatically disabled. Since there was no reason for this coupling, it was removed in Java 15.

Outlook: Compact Object Headers

For several years, work has been underway within Project Lilliput to further reduce the size of object headers in Java – initially to 64 bits and later possibly even to 32 bits.

The first milestone is within reach. In Java 24, the first experimental version of Compact Object Headers will be published through JDK Enhancement Proposal 450, thus reducing the header size to 64 bits.

Would you like to know more when the time comes? Then click here and sign up for the HappyCoders newsletter, where I will keep you regularly updated on the latest developments in the Java world.