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
-
Allocated in Native Memory
- Not part of the heap.
- Dynamically grows until system memory is exhausted.
-
No
-XX:MaxPermSize
- Removed the need to tune PermGen manually.
-
More Resilient
- Reduced OOM errors caused by class metadata.
-
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.