March 27, 2019
share:

What is Compressed Class Space?

A post in the Metaspace series:

The previous article in this series, Metaspace Architecture purposely left out the Compressed Class Space. That one further complicates the picture.

Lets talk about it.


On 64bit platforms, hotspot uses optimization techniques called Compressed Object Pointers (“CompressedOops”) and Compressed Class Pointers. Both are variants of the same thing.

Compressed pointers are a way to reference data - objects in Java heap or class metadata in Metaspace - using 32bit references even on a 64 bit platform.

This has a number of advantages, e.g. smaller pointer size resulting in reduced memory footprint and better usage of cache, and on some platforms more registers to work with.

Note: A good explanation of Compressed Object Pointers can be found here: JVM Anatomy Quark #23: Compressed References.

Also, a similar motivation drives the Linux x32 abi.

Since ultimately one needs a 64bit address to access that thing, that 32bit “pointer” is really an offset - potentially bit-shifted - into an area with a known common base.

With regards to Metaspace, we do not care about compressed oops but have to deal with compressed class pointers:

Each Java object has, in its header, a reference to a native structure living outside the Java heap in Metaspace: the Klass structure.

alt text

When using compressed class pointers, that reference is a 32bit value. In order to find the real 64bit address of that structure, we add a known common base to it, and potentially also left-shift the value by three bits:

alt text

That technique places a technical restriction on where to allocate those Klass structures:

Each possible location of a Klass structure must fall within a range of 4G (unshifted mode)|32G(shifted mode), to be reachable with a 32bit offset from a common base1.

Both restrictions mean that we need to allocate the Metaspace as one contiguous region.

When allocating memory from the system via a system API like malloc(3) or mmap(3), the addresses are chosen by the system and may be of any value fitting the type range. So on 64bit platforms, there is of course no guarantee that subsequent allocations would yield addresses within the range limit. E.g. one mmap(3) call could map at 0x0000000700000000, one at 0x0000000f00000000.

Therefore we have to use a single mmap() call to establish the region for Klass objects. So we would need to know the size of this region upfront, and it cannot be larger than 32G. It also can never be reliably extended, since the address range beyond its end may already be occupied.

These restrictions are harsh. They are also only really needed for Klass structures, not for other class metadata: only Klass instances are currently being addressed with compressed references. The other metadata get addressed with 64bit pointers and hence can be placed anywhere.

So it was decided to split the metaspace into two parts: a “non-class part” and a “class part”:

  • The class part, housing Klass structures, has to be allocated as one contiguous region which cannot be larger than 32G.

  • The non-class part, containing everything else, does not.

Terminology: The class part is called “Compressed Class Space” even though that is a bit of a misnomer since the Klass structures themselves are not compressed but the pointers to them.

The size of the compressed class space is determined by -XX:CompressedClassSpaceSize. Because we need to know the size of the class space upfront, that parameter cannot be empty. If omitted, it defaults to 1GB.

To make matters more confusing, hotspot artificially caps CompressedClassSpaceSize at 3G max - I really do not know why. So we have beside the technical limit of 32G an artificially imposed limit of 3G.

Also note that we are always talking about virtual size, not comitted size. That memory is only committed when needed. Very simplified, virtual size on most modern Operating Systems costs almost nothing, it is just an addres space reservation.

Since a Klass structure is, on average, 1K in size, a Compressed Class Space with 1G default size will be able to host about a million Klass structures (see Sizing Metaspace). This is the only real limit to the number of classes we can load.

Also note that CompressedClassPointers get disabled when we run without CompressedOops. This happens if we either manually switch CompressedOops off via -XX:-CompressedOops, or if the Java heap is larger or equal to 32G.

Implementation

In order to reuse the existing Metaspace implementation (see Metaspace Architecture), a trick was applied:

Global structures VirtualSpaceList and ChunkManager were all duplicated and now exist in two variants, a “class space” variant and a “non-class space” variant.

But since we need a contiguous address range for the class space, we cannot really use a chain of mapped regions; so the class space list is degenerated: it only contains a single node and cannot grow. That single node is gigantic compared to its brethren in the non-class list. That node is the compressed class space.

alt text

The ClassLoaderMetaspace - the per-classloader structure holding the chunks in use of this class loader - now needs two linked chunk lists, one for holding non-class chunks, one for class chunks. That also means that we double the “free” portion of the current nodes, since now we have two of them.

Switches: UseCompressedClassPointers, UseCompressedOops

-XX:+UseCompressedOops enables compressed object pointers.

-XX:+UseCompressedClassPointers enables compressed class pointers.

Both are on by default but can be switched off manually.

If compressed class pointers is switched off, we will have no compressed class space and the -XX:CompressedClassSpaceSize switch will be ignored.

-XX:+UseCompressedClassPointers requires -XX:+UseCompressedOops but not vice versa: one can run with compressed oops on without compressed class pointers. This may help in some pathological corner cases to reduce Metaspace memory footprint. In general, it is advised to leave those switches alone.

Note that compressed object pointers require Java heaps < 32G. So, if the Java heap is >= 32G, compressed oops will be switched off, which will also switch off compressed class pointers.


Update 2020-03-25: A former version of this article incorrectly stated that the maximum size of the CompressedClassSpace would be 4G. That is not correct.


  1. Unlike compressed object pointers, compressed class pointers are not shifted to extend their reach. There is no technical reason not to, since Klass allocations have a common alignment too. It just is not implemented. [return]