Memory

Metaspace explained: OutOfMemoryError: Metaspace, class loader leaks and sizing

Memory · Published 2026-10-23

Rows of small wooden card catalogue drawers with metal pulls
Metaspace is the JVM’s catalogue of loaded classes. Photo: MarkBuckawicki, Wikimedia Commons, CC0.

Every class the JVM loads needs a description: its methods, bytecode, constant pool, field layout. Since JDK 8 that metadata lives in Metaspace, a native memory area outside the Java heap. It is usually quiet, until an application loads classes faster than it unloads them.

From PermGen to Metaspace

Before JDK 8, class metadata lived in the permanent generation, a fixed-size part of the heap, and the familiar error was OutOfMemoryError: PermGen space. JEP 122 removed the permanent generation in JDK 8 and moved class metadata to native memory. The PermGen flags (-XX:PermSize, -XX:MaxPermSize) no longer apply.

In JDK 16, JEP 387 (Elastic Metaspace) reworked the allocator so that unused metaspace memory is returned to the operating system more promptly and per-class-loader overhead is reduced.

What is in it, and what is not

Metadata is allocated per class loader and freed only when the class loader itself becomes unreachable and is collected. A class cannot be unloaded on its own while its loader is alive. This is the root of every Metaspace leak.

The flags

OptionEffect
-XX:MaxMetaspaceSizeUpper bound for class metadata. The java reference states that by default the size is not limited.
-XX:MetaspaceSizeThe threshold at which reaching it first triggers a garbage collection to unload classes; the threshold is then adjusted. The default depends on the platform.
-XX:CompressedClassSpaceSizeWith compressed class pointers (the default on 64-bit), class structures go into a separate, fixed-size reserved range called the compressed class space; this option sizes it.

"Not limited" does not mean free: an unbounded Metaspace grows until the process or container runs out of memory, and the failure then looks like an OS kill rather than a clear Java error. Setting -XX:MaxMetaspaceSize to a generous but finite value turns a leak into an explicit OutOfMemoryError: Metaspace, which is far easier to diagnose.

The two error messages

The GC log shows Metaspace pressure before any error, as collections with the cause Metadata GC Threshold. A growing number of those over time is an early warning.

Monitoring

jstat

$ jstat -gc <pid> 10s

The MC and MU columns are Metaspace capacity and usage; CCSC and CCSU are the compressed class space. See the jstat reference for each column.

jcmd VM.metaspace

The most detailed view. The jcmd reference documents options including basic, show-loaders, show-classes, by-spacetype and scale:

$ jcmd <pid> VM.metaspace
$ jcmd <pid> VM.metaspace show-loaders scale=MB

show-loaders lists usage per class loader, which is exactly what you need to spot a loader that should not exist any more, or hundreds of copies of the same loader type.

Class loader views

$ jcmd <pid> VM.classloader_stats
$ jcmd <pid> VM.classloaders

The first prints, per loader, the number of classes and the metadata size; the second shows the loader hierarchy. Compare two outputs taken some time apart.

Class loading and unloading logs

-Xlog:class+load=info,class+unload=info:file=/var/log/app/classes.log

A class that is loaded again and again, with generated names that differ each time, points straight at the code that generates it.

Common causes of growth

Redeploying into a long-running server

Application servers load each deployment in its own class loader. If anything outside the application keeps a reference to any object from the old deployment, the old loader and all its classes stay. Typical culprits: a JDBC driver registered with DriverManager by the application and never deregistered, threads started by the application and never stopped, ThreadLocal values left in server threads, and shutdown hooks. Each redeploy then adds a full copy of the application's metadata.

Generated classes

Proxies, bytecode-generation libraries, expression languages, scripting engines and serialisation frameworks can generate classes at runtime. If they generate a new class per call instead of caching it (for example, compiling a script or template on each request), Metaspace grows with traffic.

Simply many classes

A very large application may need more metadata than a tight limit allows. If usage plateaus after warm-up, the limit is just too low.

Finding the leaking loader

  1. Confirm growth with jstat -gc or VM.metaspace over time.
  2. Use VM.metaspace show-loaders or VM.classloader_stats to identify the loader type that multiplies.
  3. Take a heap dump and look for instances of that class loader type. In Eclipse Memory Analyzer, the class loader explorer and duplicate classes views show loaders with the same classes.
  4. Run Path to GC Roots on an old loader instance, excluding weak references. The path ends at whatever holds it: the fix is to release that reference at undeploy. See GC roots and memory leaks and reading a heap dump.

Sizing in a memory budget

Metaspace counts against a container's memory limit like any other native memory. Measure steady-state usage after warm-up (VM.metaspace, or the Class category of Native Memory Tracking), set MaxMetaspaceSize comfortably above it, and include that figure in the total budget described in the JVM in containers.

Sources