Metaspace Explained: Evolution from PermGen to Metaspace

Illustration for Metaspace Explained: Evolution from PermGen to Metaspace
By Last updated:

Before Java 8, the JVM used a memory area called PermGen (Permanent Generation) to store class metadata. However, it came with significant limitations that caused frequent OutOfMemoryError: PermGen space. To fix these issues, Metaspace replaced PermGen starting in Java 8, offering a more flexible and reliable memory management system.

In this tutorial, we’ll explore why PermGen was removed, how Metaspace works, and how to tune it for production workloads.


Why Did PermGen Exist?

PermGen stored:

  • Class metadata (methods, fields, bytecode).
  • Static variables.
  • Interned Strings (moved to heap in Java 7).
  • Method data structures.

Problems with PermGen

  • Fixed maximum size → Could not dynamically expand.
  • Hard to tune → Developers had to guess correct -XX:MaxPermSize.
  • Frequent OOMs → Applications with many classes (e.g., Spring, Hibernate, Tomcat) would often hit errors.
  • Classloader leaks → Web apps redeploying in containers (Tomcat, JBoss) led to leaks.

Error Example:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

Introduction of Metaspace (Java 8+)

Metaspace replaced PermGen starting with Java 8.

Key Improvements

  1. Allocated in Native Memory

    • Not part of the heap.
    • Dynamically grows until system memory is exhausted.
  2. No -XX:MaxPermSize

    • Removed the need to tune PermGen manually.
  3. More Resilient

    • Reduced OOM errors caused by class metadata.
  4. String Interning & Static Variables

    • Moved to the heap, improving GC behavior.

How Metaspace Works

  • Stores class metadata in native memory.
  • Managed by Metaspace allocator inside the JVM.
  • When memory is exhausted, JVM requests more from the OS.

Tuning Metaspace

  • -XX:MetaspaceSize=128m → Initial size.
  • -XX:MaxMetaspaceSize=512m → Maximum size.
  • -XX:MinMetaspaceFreeRatio / -XX:MaxMetaspaceFreeRatio → Control resizing.

Error Example (Java 8+):

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

PermGen vs Metaspace

Feature PermGen (Java ≤7) Metaspace (Java 8+)
Location Heap memory Native memory
Strings Stored in PermGen Moved to heap
Resizing Fixed, manual tuning Dynamic, OS-managed
Errors OutOfMemoryError: PermGen OutOfMemoryError: Metaspace
Tuning Flags -XX:PermSize, -XX:MaxPermSize -XX:MetaspaceSize, -XX:MaxMetaspaceSize

Garbage Collection and Metaspace

  • Class unloading → When a classloader is garbage collected, its metadata in Metaspace is also freed.
  • Impact on GC → Heap GCs no longer include class metadata, reducing GC complexity.
  • Web apps benefit → Frequent redeployments in app servers no longer leak PermGen.

Monitoring and Tools

  • Java Flight Recorder (JFR) → Profile class loading/unloading.
  • Java Mission Control (JMC) → Inspect Metaspace usage.
  • jcmd VM.native_memory → Reports detailed Metaspace allocation.
  • VisualVM → Monitors class loading.

Pitfalls and Troubleshooting

  • Unbounded Growth → Without -XX:MaxMetaspaceSize, Metaspace may grow until OS memory is exhausted.
  • Classloader Leaks → Still possible in web apps if references prevent unloading.
  • Containerized Environments → In Docker, OS memory limits must be set properly.

Real-World Case Study

A large enterprise app running on Tomcat with hundreds of class reloads previously suffered from PermGen space errors. Migrating to Java 8 with Metaspace eliminated the issue, improving uptime and reducing manual JVM tuning.


JVM Version Tracker

  • Java 7 and earlier → PermGen, frequent OOM errors.
  • Java 8 → Metaspace introduced, no PermGen tuning needed.
  • Java 11 → Improved class unloading efficiency.
  • Java 17 → ZGC and Shenandoah compatible with Metaspace.
  • Java 21+ → Project Lilliput optimizes object headers, impacting metadata efficiency.

Best Practices

  • Set -XX:MaxMetaspaceSize in production to avoid unbounded growth.
  • Monitor class loading with JFR/JMC.
  • Clean up classloader references in web apps.
  • Use modern GCs (G1, ZGC, Shenandoah) for better Metaspace behavior.
  • Avoid excessive dynamic class generation without proper cleanup.

Conclusion & Key Takeaways

  • PermGen was replaced by Metaspace in Java 8.
  • Metaspace uses native memory, making it more flexible.
  • Eliminated many OOM issues from PermGen.
  • Still requires monitoring in large-scale apps.
  • Tuning Metaspace is crucial for containerized environments.

FAQs

1. What is the JVM memory model and why does it matter?
It defines how threads and memory interact, ensuring consistency and performance.

2. How does G1 GC differ from CMS?
G1 uses region-based collection with predictable pauses, CMS had fragmentation issues.

3. When should I use ZGC or Shenandoah?
For low-latency systems requiring sub-10ms pauses.

4. What are JVM safepoints?
Moments where JVM halts all threads for GC or optimizations.

5. How do I solve OutOfMemoryError: Metaspace?
Set -XX:MaxMetaspaceSize, fix classloader leaks, and monitor allocations.

6. How does JIT compilation interact with Metaspace?
JIT relies on class metadata stored in Metaspace for optimizations.

7. Can Metaspace still leak memory?
Yes, via classloader leaks, though less frequent than PermGen leaks.

8. How do I monitor Metaspace usage?
Use jcmd VM.native_memory or tools like JFR/JMC.

9. What’s new in Java 21 for Metaspace?
Project Lilliput improves memory efficiency by reducing object header size.

10. How does GC differ in microservices vs monoliths?
Microservices need quick startup and low memory, while monoliths optimize for throughput.