Course contents

Metaspace: where classes live

What Metaspace holds, why PermGen was removed in Java 8, where static fields and interned strings went, how classes are unloaded and how to find the classloader leaks behind OutOfMemoryError: Metaspace.

PremiumOften askedLesson16 min readSeniorReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: the blueprint library
  2. What lives in Metaspace, and what doesn't
  3. From PermGen to Metaspace: the version history
  4. Under the hood: how classes get unloaded
  5. Advanced: the classloader leak, step by step
  6. How to say it in the interview

📚 What is Metaspace?

Metaspace is the area of native memory (outside the Java heap) where HotSpot keeps class metadata: its internal description of every loaded class, including method bytecode, the constant pool, field and method layouts, and annotations. When your app loads OrderService, the OrderService objects you create go on the heap, but the description of the class itself goes into Metaspace. It replaced the old Permanent Generation (PermGen) in Java 8, grows on demand, and is capped only if you set -XX:MaxMetaspaceSize.

For this lesson, we'll pin down exactly what lives in Metaspace and what doesn't, tell the PermGen story with the right Java versions, and then spend real time on the failure you'll meet in production: OutOfMemoryError: Metaspace caused by classloaders that never die. Interviewers like this topic because the popular answers are out of date. "Static variables live in PermGen" was true in Java 6 and has been wrong for a decade.

Below the line (out of scope)

The 30-second answer

Metaspace holds class metadata in native memory: the JVM's runtime representation of each class, its methods' bytecode, constant pools and so on. It does not hold objects, and since Java 8 it does not hold static field values or interned strings: statics live on the heap with the class's java.lang.Class object, and interned strings moved to the heap in Java 7. Metaspace replaced PermGen, which was a fixed-size region capped by -XX:MaxPermSize and failed with OutOfMemoryError: PermGen space. Metaspace grows as needed and is unlimited by default, but you can cap it with -XX:MaxMetaspaceSize, and it still fails with OutOfMemoryError: Metaspace. Metadata is freed only when the classloader that defined those classes is garbage collected, so the classic cause is a classloader leak, for example after repeated hot redeploys.

What you’ll learn

  • Say what Metaspace stores (class metadata in native memory) and what it does not (objects, static field values, interned strings)
  • Tell the PermGen story: interned strings to the heap in Java 7, PermGen removed by JEP 122 in Java 8, elastic Metaspace in Java 16
  • Explain why a class can only be unloaded when its defining classloader becomes unreachable
  • Diagnose OutOfMemoryError: Metaspace from a classloader leak and cap growth with -XX:MaxMetaspaceSize
  • Watch Metaspace with jcmd VM.metaspace, jstat -gc and memory pool MXBeans

Interview radar

Asked very often: What's the difference between PermGen and Metaspace? Core follow-up: Where do static variables live in Java 8 and later? Production angle: Metaspace grows by a few MB on every redeploy until the JVM dies. What's holding it?

01Picture it first: the blueprint library

Picture a factory. The heap is the warehouse full of finished products: your objects. Metaspace is the blueprint library: one blueprint per product type, telling the factory how to build and use that type. You'd never throw a blueprint away while products made from it are still in use.

The library is organised by publisher. Each classloader gets its own shelf (HotSpot calls it an arena), and you can't remove single blueprints from a shelf. A shelf goes away only when the whole publisher goes away, which means when the classloader becomes unreachable. Keep that rule in mind; it explains every Metaspace leak.

The memory map below shows where Metaspace sits relative to the heap and the other native areas a JVM process uses.

Fig. 1

A JVM process split into the Java heap, holding objects, Class mirrors with static fields and the string pool, and native memory, holding Metaspace with its compressed class space, the code cache and thread stacks

A JVM process split into the Java heap, holding objects, Class mirrors with static fields and the string pool, and native memory, holding Metaspace with its compressed class space, the code cache and thread stacks

Notice two things. Each java.lang.Class object on the heap is paired with its metadata in Metaspace, and the static fields hang off the Class object, not off the metadata. And Metaspace is only one of several native areas, which is why "my heap is 2 GB but the process uses 3 GB" is normal.

02What lives in Metaspace, and what doesn't

The HotSpot tuning guide calls the internal representation of a Java class its class metadata, and since JDK 8 that's allocated in native memory. Here's the split I'd draw:

Item Where it lives in Java 8+
Class metadata: runtime class structure, method bytecode, constant pool, field and method descriptors, annotations Metaspace (native)
Compact per-class structures used for compressed class pointers Compressed class space, a part of Metaspace (native)
The java.lang.Class object for each class (the "mirror") Heap
Values of static fields Heap, held by the class's Class mirror
Objects referenced by static fields Heap
Interned strings and string literals Heap (since Java 7)
JIT-compiled machine code Code cache (native, not Metaspace)

Try it yourself first

Before reading on, look at this class and decide where each piece ends up: the bytecode of rateFor, the RATES field, the HashMap it points at, the "INR" key and the MAX_RATES constant.

public class ExchangeRates {
    static final int MAX_RATES = 64;
    static final Map<String, BigDecimal> RATES = new HashMap<>();

    static {
        RATES.put("INR", new BigDecimal("83.10"));
    }

    static BigDecimal rateFor(String code) {
        return RATES.get(code);
    }
}

Here's the answer:

  • The bytecode of rateFor and the static initializer, the constant pool and the description of both fields: Metaspace.
  • The RATES and MAX_RATES field slots: in the ExchangeRates.class mirror object, on the heap.
  • The HashMap, its nodes and the BigDecimal: ordinary heap objects, kept alive because a static field references them.
  • "INR": a literal, so an interned String on the heap.
  • MAX_RATES is a compile-time constant, so javac also copies the value 64 into the bytecode of every class that reads it. Callers never touch the field at runtime.

The practical upshot: a giant static cache fills the heap, not Metaspace. If someone blames Metaspace for a 3 GB static Map, they're looking in the wrong place.

Seeing it from inside the JVM

You can read Metaspace usage from Java through the memory pool MXBeans. On HotSpot the non-heap pools include Metaspace, Compressed Class Space and the code cache segments.

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;

public class NonHeapReport {
    public static void main(String[] args) {
        for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (pool.getType() == MemoryType.NON_HEAP) {
                long usedKb = pool.getUsage().getUsed() / 1024;
                long max = pool.getUsage().getMax();   // -1 means undefined (no cap)
                System.out.printf("%-32s used=%,8d KB  max=%s%n",
                        pool.getName(), usedKb, max < 0 ? "unlimited" : (max / 1024) + " KB");
            }
        }
    }
}

Notice three things when you run it:

  • Metaspace reports unlimited unless you passed -XX:MaxMetaspaceSize.
  • Compressed Class Space reports a fixed maximum, because that sub-area is reserved up front.
  • The CodeHeap pools are separate. JIT-compiled code is not in Metaspace.

03From PermGen to Metaspace: the version history

Before Java 8, HotSpot stored class metadata in the Permanent Generation, a region managed alongside the Java heap and collected during full GCs. It had a fixed maximum set by -XX:MaxPermSize. Load more classes than fit, and the JVM threw OutOfMemoryError: PermGen space, no matter how much free memory the machine had. Sizing PermGen was guesswork, and the guess was different for every app.

The move happened in two steps, and the grid below shows where each kind of data lived in each version.

Fig. 2

A grid comparing Java 6, Java 7 and Java 8 and later: class metadata moves from PermGen to native Metaspace, interned strings move from PermGen to the heap in Java 7, and static field values move from PermGen to the heap by Java 8

A grid comparing Java 6, Java 7 and Java 8 and later: class metadata moves from PermGen to native Metaspace, interned strings move from PermGen to the heap in Java 7, and static field values move from PermGen to the heap by Java 8
  • Java 7 moved interned strings out of PermGen into the main heap (the young and old generations). The JDK 7 release notes warned that apps calling String.intern() heavily would see their heap grow. The same release also moved static field values into each class's java.lang.Class object, as groundwork for what came next.
  • Java 8 removed PermGen entirely (JEP 122). Class metadata went to native memory, now called Metaspace, and class static variables went to the Java heap. -XX:PermSize and -XX:MaxPermSize were ignored with a warning in Java 8; modern JDKs reject them as unrecognized options and refuse to start, so old start scripts are worth checking.
  • Java 16 shipped Elastic Metaspace (JEP 387), a buddy allocator that returns unused Metaspace to the OS more promptly after classes are unloaded and reduces per-loader overhead.

Why bother? JEP 122's motivation is direct: tuning PermGen size was a constant chore, and the fix was to bound class metadata by available native memory instead of a fixed setting. The trade-off is that a leak no longer stops at a small wall. Without a cap, it keeps eating native memory until the container is killed or the OS refuses.

Premium lesson

Keep reading with the Java Pass

The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.

  • Under the hood: how classes get unloaded
  • Advanced: the classloader leak, step by step
  • How to say it in the interview

From ₹299 · one-time payment · No auto-renewal

Interview questions for this topicComing soon

Further reading (9)