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 need to add a known common base to it:

alt text

That technique places restrictions on where to allocate those Klass structures:

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

Both restrictions mean that we would have 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 a 4G range. E.g. one mmap(3) call could map at 0x0000000a00000000, one at 0x0000000c00000000.

Therefore we have to use a singe mmap() call to establish the region for Klass objects. So we would need to know the size of this region upfront. It also cannot be larger than 4GB, obviously. 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 4G.

  • 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. Please keep in mind that this is virtual size, not comitted size, so we will not use 1GB of memory upfront.

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.


  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]