Metaspace vs PermGen — Why Java 8 Replaced PermGen
Fast recall
Quick revision
Core answer
-XX:MaxPermSize, default 64–128MB), causing the notorious `OutOfMemoryError: PermGen space` when classloaders proliferated (app servers, hot reloads, dynamic class generation). Metaspace (Java 8+) replaced PermGen and: (1) lives in native memory (outside the Java heap), (2) grows dynamically by default (no hard cap unless -XX:MaxMetaspaceSize is set), (3) only stores class metadata (not interned strings or static fields). Where things moved (Java 7–8 transition): interned strings → moved to the regular heap (Java 7); static fields → moved to the heap (Java 8); class metadata (method bytecode, constant pools, annotations) → stayed in PermGen → moved to Metaspace (Java 8). Why PermGen was removed: (1) fixed size required tuning — every app needed -XX:MaxPermSize adjusted for its classloader count; (2) `OutOfMemoryError: PermGen space` was extremely common in app servers (Tomcat, JBoss) on hot redeploy; (3) inflexibility — a region inside the heap that didn't behave like other regions complicated GC. What still goes wrong with Metaspace: `OutOfMemoryError: Metaspace` can still happen — typically from classloader leaks (e.g., webapp not unloading on redeploy, frameworks generating proxies infinitely, dynamic languages on JVM). Always set -XX:MaxMetaspaceSize=512m (or similar) in production to fail fast instead of consuming all native memory. For interviews: lead with 'PermGen was fixed-size + heap-resident, caused frequent OOMs; Metaspace is native, dynamic, only holds class metadata; classloader leaks still cause Metaspace OOMs'.Whether the candidate knows what changed in Java 7–8 (interned strings, static fields, class metadata moved), why PermGen was removed, and that Metaspace OOMs still happen via classloader leaks
Saying Metaspace is on the heap (it's native memory). Not knowing where interned strings moved (heap, in Java 7). Thinking Metaspace can never OOM. Not setting MaxMetaspaceSize in production
Discuss classloader leaks as the modern cause of Metaspace OOMs, mention dynamic proxy / bytecode generation issues (Spring, Hibernate, Mockito), discuss why apps servers were the worst-affected by PermGen, explain how to diagnose Metaspace leaks with jcmd / VisualVM
Interview-ready guide
Interview answer
PermGen vs Metaspace — the change
PermGen (Java 7 and earlier)
- Heap-resident region
- Fixed size (
-XX:MaxPermSize) - Held class metadata + interned strings + static fields
- Default ~64–128MB — too small
OutOfMemoryError: PermGen spacewas famous
Metaspace (Java 8+)
- Native memory (outside Java heap)
- Grows dynamically by default (no cap)
- Holds only class metadata
- New flag:
-XX:MaxMetaspaceSize=512m
Migration timeline
Java 7
- Interned strings moved from PermGen to regular heap
- String pool now subject to normal GC
Java 8
- Static fields moved to regular heap (with Class objects)
- Class metadata moved to Metaspace
- PermGen removed entirely
-XX:MaxPermSizesilently ignored
What Metaspace stores
- Class metadata (method bytecode, constant pools, annotations)
- Just that
- Smaller scope than PermGen
What's NOT in Metaspace
- Interned strings (heap, since Java 7)
- Static fields (heap, since Java 8)
- JIT-compiled code (separate Code Cache)
Why PermGen was removed
- 1Fixed size required tuning (every app needed a different
-XX:MaxPermSize) - 2App server hot redeploy = OOM (Tomcat / JBoss / WebSphere reload webapps; classloader leaks accumulate)
- 3Inflexibility (fixed region inside heap; conflicted with modern GC algorithms)
- 4Default too small (64MB was fine in 2005; modern frameworks need more)
Why Metaspace can still OOM
Almost always classloader leak. Metaspace dynamic, but native memory is finite.
Common leaks
- 1JDBC drivers register with DriverManager (parent classloader); webapp can't unload. Fix: deregister in ServletContextListener.
- 2ThreadLocal not cleared on thread pool workers. Fix:
tl.remove()in finally. - 3Logging framework references. Fix: parent-loaded or explicit unregister.
- 4Dynamic proxies (Spring CGLIB, Hibernate, Mockito generate classes). Fix: mockito-inline.
- 5JSP recompilation (each edit = new class).
Diagnosis
jcmd— total Metaspace usageVM.metaspace jcmd(Java 9+) — list classloaders; look for duplicatesVM.classloader_stats jstat -class— live class loading1000 - Heap dump + Eclipse MAT — 'Leak Suspects' / 'Duplicate Classes'
Production tuning
-XX:MaxMetaspaceSize=512m
-XX:MetaspaceSize=128m
-XX:+HeapDumpOnOutOfMemoryError
-Xlog:gc*,gc+metaspace=infoContainer: heap + Metaspace + native overhead < container limit.
Common bugs / misconceptions
- 1Saying Metaspace is on the heap (it's native memory)
- 2Not knowing where interned strings moved (Java 7)
- 3Thinking Metaspace can never OOM
- 4Not setting MaxMetaspaceSize (default is unlimited → leak → native exhaustion → JVM dies)
- 5Still using -XX:MaxPermSize (silently ignored in Java 8+)
- 6Underestimating Mockito Metaspace usage in large test suites
For interviews
Lead with: PermGen was fixed-size, heap-resident, held class metadata + interned strings + static fields, caused frequent OOMs in app servers; Java 8 replaced it with Metaspace (native, dynamic, only class metadata).
Mention: migration timeline, always set MaxMetaspaceSize, classloader leaks still cause OOMs, common leak sources, diagnosis tools.
To stand out: app server hot redeploy was the killer use case for PermGen OOMs; mockito-inline tip for large test suites; container memory budgeting; jcmd VM.classloader_stats (Java 9+).
Recommendation: PermGen (Java 7 and earlier) was a fixed-size heap region holding class metadata, interned strings, and static fields. Its fixed cap (-XX:MaxPermSize, default ~64–128MB) caused the notorious `OutOfMemoryError: PermGen space` — especially in application servers (Tomcat, JBoss) doing hot redeploy, where classloader leaks (JDBC drivers registered with DriverManager, ThreadLocal not cleared, logging context references) prevented old webapp classloaders from being garbage collected, accumulating PermGen until OOM. Java 8 replaced PermGen with Metaspace, which lives in native memory (outside the Java heap), grows dynamically by default, and only stores class metadata (method bytecode, constant pools, annotations). The migration happened in two phases: Java 7 moved interned strings to the regular heap (subject to normal GC); Java 8 moved static fields to the regular heap (with their Class objects) and class metadata to Metaspace. Old -XX:PermSize / -XX:MaxPermSize flags are silently ignored in Java 8+. Metaspace can still OOM (OutOfMemoryError: Metaspace) — the cause is almost always a classloader leak, with common sources being JDBC drivers (deregister in ServletContextListener.contextDestroyed), ThreadLocal not cleared (always remove() in finally), dynamic class generation (Spring CGLIB, Hibernate, Mockito — use mockito-inline for large test suites), and JSP recompilation. Always set `-XX:MaxMetaspaceSize=512m` (or appropriate) in production to fail fast on leaks instead of consuming all native memory or being killed by the Linux OOM-killer. Diagnose with jcmd and VM.classloader_stats (Java 9+), and analyze heap dumps in Eclipse MAT for duplicate classes (sign of classloader leaks). For containerized deployments, budget: heap (-Xmx) + Metaspace + native overhead must fit in the container memory limit. The mental model: PermGen was fixed-size and heap-resident and constantly OOM'd; Metaspace is dynamic and native-resident, but classloader leaks still strike — cap it explicitly and clean up references aggressively in long-running app servers.
Metaspace vs PermGen — Why Java 8 Replaced PermGen
Direct Answer
PermGen (Permanent Generation) was a fixed-size heap region (Java 7 and earlier) holding class metadata, interned strings, and static fields. It had a fixed maximum size (-XX:MaxPermSize, default 64–128MB), causing the notorious OutOfMemoryError: PermGen space when classloaders proliferated (app servers, hot reloads, dynamic class generation). Metaspace (Java 8+) replaced PermGen and: (1) lives in native memory (outside the Java heap), (2) grows dynamically by default (no hard cap unless -XX:MaxMetaspaceSize is set), (3) only stores class metadata (not interned strings or static fields). Where things moved (Java 7–8 transition): interned strings → moved to the regular heap (Java 7); static fields → moved to the heap (Java 8); class metadata (method bytecode, constant pools, annotations) → stayed in PermGen → moved to Metaspace (Java 8). Why PermGen was removed: (1) fixed size required tuning — every app needed -XX:MaxPermSize adjusted for its classloader count; (2) OutOfMemoryError: PermGen space was extremely common in app servers (Tomcat, JBoss) on hot redeploy; (3) inflexibility — a region inside the heap that didn't behave like other regions complicated GC. What still goes wrong with Metaspace: OutOfMemoryError: Metaspace can still happen — typically from classloader leaks (e.g., webapp not unloading on redeploy, frameworks generating proxies infinitely, dynamic languages on JVM). Always set -XX:MaxMetaspaceSize=512m (or similar) in production to fail fast instead of consuming all native memory. For interviews: lead with 'PermGen was fixed-size + heap-resident, caused frequent OOMs; Metaspace is native, dynamic, only holds class metadata; classloader leaks still cause Metaspace OOMs'.
Common Mistakes & Pitfalls
Saying Metaspace is on the heap (it's native memory). Not knowing where interned strings moved (heap, in Java 7). Thinking Metaspace can never OOM. Not setting MaxMetaspaceSize in production
Check your understanding
Answer these without looking back at the article.
- Why was PermGen removed in Java 8?
- Where did interned strings move to and when?
- Why does Metaspace still cause OutOfMemoryError despite being dynamic?
- How do you diagnose a Metaspace classloader leak?
- Why should you always set -XX:MaxMetaspaceSize in production?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.