March 28, 2019

Sizing Metaspace

MaxMetaspaceSize and CompressedClassSpaceSize are our knobs to control Metaspace size.

Now, these parameters can be a tad confusing. For one, there are two of them, and they have subtly different meanings, and they influence each other.

So lets take a closer look. We will explain how those parameters work in detail. Then, we will analyze how much Metaspace a single class on average needs. Finally, we will attempt to derive some rough rules of thumb and also examine what the default behavior is.

Note: Recommended reading: Part 3: What is Compressed Class Space.

The knobs

Let’s reiterate:

  • MaxMetaspaceSize : This is a “soft” limit on the maximum of committed Metaspace. It includes both non-class and class space. It is “soft” in the sense as there is not pressing technical reason to this limit beyond the wish to have a limit. It is entirely optional and in fact off by default.

  • CompressedClassSpaceSize : This is a hard limit, defining the virtual size of the compressed class space. “Hard” since we need to fix it at VM start and can never change it. If we omit it, it defaults to 1G.

This image illustrates how these limits work:

MaxMetaspaceSize and CompressedClassSpaceSize

The red portion is the sum of the committed portion of Metaspace, including both non-class space nodes and the one giant class-space node. This sum is limited by -XX:MaxMetaspaceSize. Attempting to commit more memory than -XX:MaxMetaspaceSize will result in an OutOfMemoryError(“Metaspace”).

The intent of -XX:MaxMetaspaceSize is of course simple: to have a max limit to committed Metaspace size. It will never grow beyond this point.

On the other hand, -XX:CompressedClassSpaceSize determines the reserved size of the one giant class-space node. It includes both the committed part and the (blue) still uncommitted part. If that node is full, we will get an OutOfMemoryError(“Compressed Class Space”).

Note: as mentioned in Part 3, we have the switch UseCompressedClassPointers. It is on by default, but if switched off there will be no Compressed Class Space, -XX:CompressedClassSpaceSize will ignored and Metaspace will be only limited by -XX:MaxMetaspaceSize. This whole thing becomes vastly simpler.

So, what does that mean?

When a Java class is loaded, it needs memory from both non-class space and class space. Since the latter is always limited, we effectively always have a cap in place to Metaspace growth even if -XX:MaxMetaspaceSize is left unlimited.

Where this cap hits depends on the size of the loaded classes (more below), which determines the ratio between non-class space and class-space usage.

For example, lets assume a (reasonable) ratio of 1:5 class:non-class space size per class.

That means for the default setting of -XX:CompressedClassSpaceSize of 1G we would cap out at ~6G: filling 1G default-sized Compressed Class Space to the brim would cause ~5G of non-class space to be allocated too.

How much Metaspace does one class need?

For each loaded class there will be space allocated from class metadata from both class and non-class space. Now, what goes where?:

How much space does a Java class need?

Into Class Space:

Into Class Space goes the Klass structure, which is fixed-sized.

That is followed by two variable sized structures, the vtable and the itable. Size of the former grows with the number of methods, size of the latter with the number of inherited interface methods from implemented interfaces.

That in turn is followed by a map describing the positions of Object-referencing members in the Java class, the non-static Oopmap. This structure is also variable sized though typically very small.

Both vtable and itable are typically small but can grow to enormous proportions for weird large classes. A vtable for a class with 30000 methods will be 240k in size, an itable when deriving from an interface with 30000 methods also. But these are test cases, apart from automatic code generation one will not find such classes in the wild.

Into Non-Class Space:

Into Non-Class Space go a lot of things, among them as largest contributors:

  • the constant pool, which is variable sized.

  • meta data for any of the class methods: the ConstMethod structure with a lot of associated, largely variable-sized embedded structures like the method bytecode, the local variable table, the exception table, parameter infos, signature etc.

  • Runtime method data used to control the JIT

  • Annotations

Note: Structures stored in Metaspace all derive from MetaspaceObj, so following that type tree will give you a good impression what lives in Metaspace.

Size ratio between class- and non-class-space

Lets see what the ratio between class- and non-class-space is for typical applications.

Here we have a WildFly1 server, standalone, 16.0.0, running on SapMachine 11. No apps loaded, just the bare server after startup. We examine how much metaspace we need and from that how much space per class, on average, is needed. We measure with jcmd VM.metaspace .

loader #classes non-class space (avg per class) class space (/avg per class) ratio non-class/class
all 11503 60381k (5.25k) 9957k (.86k) 6.0 : 1
bootstrap 2819 16720k (5.93k) 1768k (0.62k) 9.5 : 1
app 185 1320k (7.13k) 136k (0.74k) 9.7 : 1
anonymous 869 1013k (1.16k) 475k (0.55k) 2.1 : 1

What does this tell us?

  • For standard classes (assuming classes loaded by bootstrap and app loaders are considered standard), we come to an average of ~5-7k of non-class space and 600-900 bytes class space per class.

  • Anonymous (lambda) classes are much smaller, no surprise, but interestingly ratio between class and non-class space usage is also warped: we need much more class space in relation to non-class space. That is not surprising since Lambda classes are minuscule, but the overhead for the Klass structure cannot shrink below sizeof(Klass) structure itself. So, we come to about 1k non-class space, .5k class space.

Note that in our case the anonymous classes do not matter much. For their impact to be noticeably, a lot more anonymous classes would have to be allocated.

Metaspace default size

If you do not set any limits to Metaspace, how many classes could it accomodate?

MaxMetaspaceSize is by default unlimited whereas CompressedClassSpaceSize is sized to 1G. This means the only limit we would hit is CompressedClassSpaceSize.

Using the example numbers above (~5-7k non-class space, 600-900 bytes class space), we could accommodate about 1..1,5 million classes under ideal circumstances (if there were no fragmentation, no waste) before hitting an OutOfMemoryError in Compressed Class Space. That is a lot of metadata and quite certainly overkill.

However, CompressedClassSpaceSize is just reserved space, not committed. So it “does not hurt as much” - we only really2 use the committed portion of the class space.

Limiting Metaspace

Disclaimer: Do not use any rules you find on the Internet blindly, especially not in production!

Please note that there are actually not that many choices here. Sure you can limit Metaspace growth. But if you do, and your program needs more space for class metadata than you allow it to have, it will just fail with an OOM. Short before rewriting the program to load less classes there is nothing you can do.

Compare this with Java heap: you can increase or decrease the Java heap within certain limits without effecting the programs primary function - there is a certain flexibility here which we have not with Metaspace.

So why would you want to limit Metaspace?

  • As a warning system, a “sanity envelope”, to know when Metaspace consumption is larger than it should be and someone should take a look.
  • Sometimes it may be needed to limit virtual memory size (aka ps “SIZE” column). While usually the resident set size (RSS) is the real interesting stat, the virtual memory size may cause the VM process to hit quotas.

Note: JDK version dependency: Metaspace in JDK 8 suffers way more from fragmentation than in JDK 11 or later. Keep that in mind and add a healthy margin if you run on older JDKs.

If you want to limit Metaspace size to have a “sanity envelope” and do not care about virtual process size, it is best to just set MaxMetaspaceSize and leave CompressedClassSpaceSize untouched. If left alone, CompressedClassSpaceSize will default to ~80% of MaxMetaspaceSize - which may be overcompensating a bit but remember this is just reserved space, not committed.

The only reason to decrease CompressedClassSpaceSize, in addition to MaxMetaspaceSize, would be to decrease the virtual memory size of the VM process too. But remember that if you set CompressedClassSpaceSize too low you may fill out the Compressed Class Space before using up MaxMetaspaceSize. That said, a ratio of 1:2 (CompressedClassSpaceSize = MaxMetaspaceSize / 2) should be safe for most cases.

So how large should you set MaxMetaspaceSize? First calculate the average expected Metaspace usage. As a first guideline you can use the numbers given above, with a safety margin - ~1K class space per class, ~8K non-class space per class3 - and multiply those with the numbers of classes you expect to load.

So for example, if your application plans on loading 10000 classes, you would in theory only need 10M of class space, 80M of non-class space.

Now you need to factor in a safety margin. Factor 2 would be a safe limit for most scenarios. You can of course go lower and try your luck, but be prepared to handle Metaspace OOMs by either changing your code or increasing MaxMetaspaceSize.

Factor 2 would bring us to 20M class space, 160M non-class space, which comes to a total Metaspace size of 180M. So, -XX:MaxMetaspaceSize=180M would be a good first approximation.

Your mileage may vary however! When in doubt, measure the Metaspace usage of your app. I will show you how in a future post.

  1. Throughout these posts I will reuse the same WildFly server (16.0.0) - no special reason apart from WildFly being the first J2EE server I found to be able to run on JDK 11. [return]
  2. Which is of course a gross simplification - even uncommitted memory needs resources, e.g. page table space. [return]
  3. All numbers are for 64bit VMs. [return]