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.
On this page
📚 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)
- Stack frames, heap objects and the string pool: Stack vs heap
- How the collector decides what's reachable: How garbage collection works
- Young and old generations on the heap: Generational collection
- Classloader delegation and the class loading lifecycle in depth: a later lesson in this module
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: Metaspacefrom a classloader leak and cap growth with-XX:MaxMetaspaceSize - Watch Metaspace with
jcmd VM.metaspace,jstat -gcand 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.
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
rateForand the static initializer, the constant pool and the description of both fields: Metaspace. - The
RATESandMAX_RATESfield slots: in theExchangeRates.classmirror object, on the heap. - The
HashMap, its nodes and theBigDecimal: ordinary heap objects, kept alive because a static field references them. "INR": a literal, so an internedStringon the heap.MAX_RATESis a compile-time constant, sojavacalso copies the value64into 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:
Metaspacereportsunlimitedunless you passed-XX:MaxMetaspaceSize.Compressed Class Spacereports a fixed maximum, because that sub-area is reserved up front.- The
CodeHeappools 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.
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'sjava.lang.Classobject, 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:PermSizeand-XX:MaxPermSizewere 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)
- JEP 122: Remove the Permanent Generation
- JEP 387: Elastic Metaspace
- JDK 7 release notes: interned strings moved out of the permanent generation
- JLS §12.7 Unloading of Classes and Interfaces (Java SE 21)
- HotSpot GC Tuning Guide, Other Considerations: Class Metadata (JDK 21)
- Troubleshooting memory leaks: OutOfMemoryError Metaspace and Compressed class space (JDK 21)
- The java command: -XX:MaxMetaspaceSize, -XX:MetaspaceSize, -verbose:class (JDK 21)
- The jcmd command: VM.metaspace, VM.classloader_stats (JDK 21)
- The jstat command: -gc and -gcmetacapacity (JDK 21)