Introduction
Java's Metaspace stores class metadata and grows dynamically. When applications generate many classes at runtime -- through frameworks like Spring, Hibernate, or bytecode manipulation libraries -- Metaspace can fill up and trigger OutOfMemoryError: Metaspace.
This error is common in large Spring Boot applications, applications using extensive proxy generation, or frameworks that create classes dynamically at runtime.
Symptoms
- Application crashes with "java.lang.OutOfMemoryError: Metaspace"
- Error occurs after the application has been running for hours or days
- Frequent Full GC events with little memory reclaimed before the crash
Common Causes
- Framework generates too many dynamic classes (proxies, CGLIB, bytecode enhancement)
- Class loader leak: dynamically loaded classes are never garbage collected
- MaxMetaspaceSize is set too low or uses default unbounded growth
Step-by-Step Fix
- 1.Set appropriate Metaspace limits: Configure Metaspace size to prevent unbounded growth.
- 2.```bash
- 3.# Set MaxMetaspaceSize to a reasonable limit:
- 4.java -XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=256m -jar app.jar
# In Docker: ENV JAVA_OPTS="-XX:MaxMetaspaceSize=512m -XX:MetaspaceSize=256m" ```
- 1.Diagnose class loader leaks: Use jcmd to analyze Metaspace usage.
- 2.```bash
- 3.# Get Metaspace usage:
- 4.jcmd <pid> VM.metaspace
# Check for class loader leaks: jcmd <pid> VM.classloader_stats
# Generate heap dump for analysis: jmap -dump:live,format=b,file=metaspace.hprof <pid> ```
- 1.Enable class unloading to reclaim Metaspace: Allow JVM to unload unused classes.
- 2.```bash
- 3.# Enable class unloading with G1GC:
- 4.java -XX:+UseG1GC -XX:+ClassUnloading -XX:+ClassUnloadingWithConcurrentMark -jar app.jar
# For ZGC (Java 15+): java -XX:+UseZGC -jar app.jar # ZGC does concurrent class unloading by default ```
- 1.Monitor Metaspace in production: Set up alerting before Metaspace reaches critical levels.
- 2.```java
- 3.import java.lang.management.ManagementFactory;
- 4.import java.lang.management.MemoryPoolMXBean;
for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) { if ("Metaspace".equals(pool.getName())) { long used = pool.getUsage().getUsed(); long max = pool.getUsage().getMax(); System.out.printf("Metaspace: %.1f MB / %.1f MB%n", used / 1024.0 / 1024.0, max / 1024.0 / 1024.0); } } ```
Prevention
- Set -XX:MaxMetaspaceSize to prevent unbounded growth in production
- Use G1GC with class unloading enabled for dynamic class-heavy applications
- Monitor Metaspace usage with JMX and set alerts at 80% capacity
- Profile application with JFR (Java Flight Recorder) to identify excessive class generation