java compressed oops hero imagejava compressed oops hero image
HappyCoders Glasses

Compressed Oops in Java

Sven Woltmann
Sven Woltmann
Last update: November 27, 2024

In this article, you will learn how “Compressed Oops” is used on a 64-bit system to represent references to Java objects with only 32 bits instead of 64, and how this significantly reduces the memory requirements of a Java application.

What is a 64-Bit System?

A 64-bit system is characterized by the fact that pointers to addresses in memory are 64-bit long. This means we can address 264 bytes = 16 exabytes = 18,446,744,073,709,551,616 bytes.

Nowadays, there are practically no programs that require this much memory. In ten years, we might smile at this statement ;-)

Here is the graphical representation of such a 64-bit pointer:

64-bit pointer

Now you could consider dividing these 64 bits in half and storing not just one but two pointers with the same amount of memory:

Two 32-bit pointers

However, with 32 bits, we can address only 232 bytes = 4 GB. That is not enough for many applications.

The JDK developers have, therefore, devised a clever trick (“Compressed Oops”) to address not just 4 GB but 32 GB using only 32 bits. And that, in turn, is sufficient for most applications today.

How does Compressed Oops Work?

Compressed Oops (OOP stands for “ordinary object pointer”) allows an address space of 235 = 32 gigabytes to be addressed using 32-bit pointers.

How is that possible? To address 32 gigabytes, we would actually need 35 bits. But we cannot store 35 bits in a 32-bit int; for that, we would need the next largest data structure, a 64-bit long:

35-bit pointer

So we would have wasted 29 bits. Then we could have stuck with 64-bit pointers.

It's time to use a trick!

First of all, we position all Java objects at memory addresses that are divisible by eight. Eight is equal to 23, which means that the last 3 bits of a pointer are always 0. Therefore, only the upper 32 bits contain relevant information:

32 relevant bits in a 35-bit pointer

Since we know that the last three bits are always 0, we do not need to store them every time. Therefore, the 35-bit memory address can be shifted right by three bits without any loss of information and thus stored in a 32-bit field:

Compressed Oops in Java: 35-bit pointer compressed to 32 bits

To access the uncompressed memory address again later, the 32 bits simply have to be shifted left by three bits.

Compressed Oops are Enabled by Default

On a 64-bit system with a maximum of 32 GB heap, Compressed OOPs are enabled by default. If the heap size is more than 32 GB, we cannot use Compressed Oops (because they can only address 32 GB).

You can turn off Compressed Oops with the following VM option:

-XX:-UseCompressedOops

Why should one do that?

Compressing and decompressing pointers costs time. Not much, as the required shift operation can usually be performed in a single CPU cycle. However, those who want to squeeze the last bit of performance out of their application and are willing to accept the increased memory requirement (all pointers occupy 64 instead of 32 bits) can consider this option.

Until Java 14, the activation of Compressed Class Pointers was coupled with the activation of Compressed OOPs. If you deactivated Compressed OOPs, Compressed Class Pointers were automatically deactivated as well. Since there was no reason for this coupling, it was removed in Java 15.

Conclusion

Compressed Oops allows you to encode memory addresses with only 32 bits instead of 64 on a 64-bit system with a heap size of up to 32 GB. Since there is at least one pointer to every active Java object in an application, Compressed Oops significantly reduces the memory requirements of an application.

Want to keep up to date with all the new Java features? Then click here to sign up for the HappyCoders newsletter.