Skip to content

Java Object Headers and Compressed Class Pointers

Processor chip on a circuit board with orange glowing conductor tracks

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?

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 class 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 – I’ll explain how in the next section.

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.

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 0b01 (unlocked) to 0b00 (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 0b10.

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 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, because compressed pointers can be used to address around 4 to 6 million classes – and even large Java applications rarely use more than 100,000 classes. For this reason, this option is marked as deprecated in Java 25. In Java 27, the uncompressed class-pointer mode was removed, so that -XX:-UseCompressedClassPointers no longer has any effect.

Outlook: Compact Object Headers

In Java 24, Compact Object Headers – developed as part of Project Lilliput – were introduced as an experimental feature; they make it possible to reduce the header size to 64 bits. In Java 25, Compact Object Headers were finalized, and in Java 27 they will be enabled by default.

Did this article answer your questions? Then I’d be happy about a review on my ProvenExpert profile – it helps other developers find this content.

👉 Leave a review

Java evolves faster than you might think – a new release ships every six months. To make sure you don’t miss a feature, click here and sign up for the HappyCoders newsletter.

👉 Newsletter Sign-up

Want Even More Knowledge?

My blog features many articles on Java, software architecture, and performance — from foundational concepts to advanced patterns.

If you want to go deeper, check out my trainings: hands-on, easy to understand, and directly applicable to your day-to-day project work. Instead of theory, I teach principles that help you write code that is better, more maintainable, and more performant in the long run.

Explore the Java Trainings

Become a Better Java Developer

My free newsletter keeps you ahead. Modern Java: new versions & features, performance, and JVM insights – once a month.

Search