JVM Compressed OOPs let you use less memory on 64-bit JVMs by storing pointers to objects in a more compact form.

Let’s see it in action. Imagine we have a simple Java class:

class MyObject {
    int a;
    long b;
    String c;
    Object d;
}

We’ll create a bunch of these objects and then inspect the memory usage.

First, without compressed OOPs enabled. We’ll use -XX:-UseCompressedOops to explicitly disable it (though it’s often enabled by default).

java -XX:-UseCompressedOops -XX:+PrintFlagsFinal -version | grep UseCompressedOops

This will show intx UseCompressedOops := 0 (meaning disabled). Now, let’s run a simple JMH benchmark or a custom test that creates many MyObject instances and measures heap usage. For simplicity, let’s use a basic loop and a heap dump.

import java.util.ArrayList;
import java.util.List;

public class CompressedOopTest {
    public static void main(String[] args) {
        List<MyObject> objects = new ArrayList<>();
        int count = 1_000_000; // One million objects
        for (int i = 0; i < count; i++) {
            objects.add(new MyObject());
        }
        System.out.println("Objects created. Press Enter to exit...");
        try {
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class MyObject {
    int a;
    long b;
    String c;
    Object d;
}

Compile and run this:

javac CompressedOopTest.java
java -XX:-UseCompressedOops CompressedOopTest

After running and pressing Enter, you’d typically take a heap dump using jmap or your IDE’s profiling tools and analyze it. You’ll observe a certain amount of memory attributed to the MyObject instances and the ArrayList holding them. The key observation is how much space the references (String c, Object d) take up. On a 64-bit system, a full 64-bit pointer is 8 bytes. If you have many objects referencing other objects, these 8-byte pointers add up quickly.

Now, let’s enable compressed OOPs. We do this by not explicitly disabling it, as it’s usually on by default for heaps under 32GB. We can verify it’s enabled:

java -XX:+PrintFlagsFinal -version | grep UseCompressedOops

This should show intx UseCompressedOops := 1. Run the same Java program:

java CompressedOopTest

Analyze the heap dump again. You’ll notice a significant reduction in memory usage for the same number of objects. The String c and Object d fields, which are references, now occupy less space.

How it works internally:

Compressed OOPs is a JVM feature that allows the JVM to represent object pointers (references) using 32-bit addresses instead of 64-bit addresses when the heap is within a certain size limit (typically 32GB). It achieves this by using a base address for the heap and then storing offsets from that base. Think of it like this: instead of giving someone the full latitude and longitude of a city (8 bytes), you give them the city’s name and a small offset from a known landmark in that region (4 bytes).

The JVM dynamically calculates this base address. For an object reference obj, the JVM internally stores (obj - heap_base) / pointer_scale. When the JVM needs to dereference the pointer (i.e., access the object it points to), it reverses this calculation: heap_base + compressed_pointer * pointer_scale. The pointer_scale is typically 1 for compressed object pointers.

This compression is only effective if the heap base address is aligned such that all object addresses within the heap can be represented by 32-bit offsets. The JVM handles this alignment automatically. The magic is that most modern CPUs, even 64-bit ones, can efficiently handle 32-bit addresses, so there’s often little to no performance penalty for this memory saving.

The levers you control:

  1. -XX:+UseCompressedOops: Explicitly enables compressed object pointers. This is the default on most modern JVMs for heaps up to 32GB.
  2. -XX:-UseCompressedOops: Explicitly disables compressed object pointers. Use this if you suspect compressed OOPs is causing issues or for specific benchmarking.
  3. -XX:CompressedClassSpaceSize=...: Controls the size of the memory region used for compressed class pointers. This is a related but distinct feature.

The one thing most people don’t know:

Compressed OOPs is not just about object references; it also applies to class pointers. The JVM uses a similar mechanism for klass pointers (which point to the class metadata of an object). This further reduces memory overhead. The entire mechanism relies on the heap base address being aligned to a power of 2, allowing all addresses within the heap to be represented by 32-bit offsets from that base. If the heap base address isn’t aligned correctly, compressed OOPs cannot be used.

The next concept you’ll likely encounter is JVM garbage collection tuning, specifically how heap size and object density impact GC performance.

Want structured learning?

Take the full Jvm course →