Metaspace vs PermGen in Java

A comprehensive comparison between PermGen (Permanent Generation) and Metaspace memory areas in the JVM.

1. Overview and Evolution

public class MemoryEvolution {
public static void main(String[] args) {
System.out.println("Java Memory Area Evolution:");
System.out.println("Java 7 and earlier: PermGen");
System.out.println("Java 8 and later: Metaspace");
// Check current Java version
System String javaVersion = System.getProperty("java.version");
System.out.println("Running Java: " + javaVersion);
if (javaVersion.startsWith("1.8") || javaVersion.startsWith("8")) {
System.out.println("Using Metaspace (Java 8+)");
} else if (javaVersion.startsWith("1.7") || javaVersion.startsWith("7")) {
System.out.println("Using PermGen (Java 7)");
}
}
}

2. PermGen (Permanent Generation) - Java 7 and earlier

Location: Part of JVM Heap memory

public class PermGenDemo {
// These would be stored in PermGen in Java 7:
// Class metadata
private static class PermanentClass {
static final String CONSTANT = "This stays in PermGen";
}
// String literals from pool
private String stringLiteral = "This string in PermGen";
// Static variables
private static List<String> staticList = new ArrayList<>();
// Method area information
public static void main(String[] args) {
// In Java 7, this could cause java.lang.OutOfMemoryError: PermGen space
loadMultipleClasses();
}
public static void loadMultipleClasses() {
for (int i = 0; i < 100000; i++) {
// Dynamic class loading - each class consumes PermGen
try {
Class<?> dynamicClass = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
// PermGen JVM Flags (Java 7):
// -XX:PermSize=64m       // Initial PermGen size
// -XX:MaxPermSize=256m   // Maximum PermGen size
// -XX:+PrintGCDetails    // Monitor GC activity

3. Metaspace - Java 8 and later

Location: Native memory (off-heap)

public class MetaspaceDemo {
// In Java 8+, these are stored in Metaspace:
// Class metadata
private static class MetaClass {
static final String CONSTANT = "This stays in Metaspace";
}
// Method metadata
public void methodWithMetadata() {
// Method information stored in Metaspace
System.out.println("Method metadata in Metaspace");
}
public static void main(String[] args) {
System.out.println("Metaspace Demo - Java 8+");
// Monitor Metaspace usage
printMetaspaceInfo();
// Demonstrate class loading behavior
demonstrateClassLoading();
}
public static void printMetaspaceInfo() {
// Get Metaspace information (requires MXBean)
java.lang.management.ClassLoadingMXBean classLoadingMxBean = 
java.lang.management.ManagementFactory.getClassLoadingMXBean();
System.out.println("Loaded Class Count: " + classLoadingMxBean.getLoadedClassCount());
System.out.println("Total Loaded Classes: " + classLoadingMxBean.getTotalLoadedClassCount());
System.out.println("Unloaded Classes: " + classLoadingMxBean.getUnloadedClassCount());
}
public static void demonstrateClassLoading() {
// Create multiple classes to observe Metaspace behavior
for (int i = 0; i < 1000; i++) {
createDynamicClass(i);
}
}
private static void createDynamicClass(int index) {
// Simulate dynamic class creation
String className = "DynamicClass" + index;
// In real scenarios, this would be done with bytecode manipulation
}
}
// Metaspace JVM Flags (Java 8+):
// -XX:MetaspaceSize=64m          // Initial Metaspace size
// -XX:MaxMetaspaceSize=256m      // Maximum Metaspace size  
// -XX:MinMetaspaceFreeRatio=40   // Minimum free ratio after GC
// -XX:MaxMetaspaceFreeRatio=70   // Maximum free ratio after GC

4. Key Differences Comparison

public class PermGenVsMetaspace {
public static class Comparison {
// Feature comparison between PermGen and Metaspace
public static void main(String[] args) {
System.out.println("=== PermGen vs Metaspace Comparison ===");
System.out.println("\n1. LOCATION:");
System.out.println("PermGen: Part of JVM Heap memory");
System.out.println("Metaspace: Native memory (off-heap)");
System.out.println("\n2. MEMORY MANAGEMENT:");
System.out.println("PermGen: Fixed size, requires manual tuning");
System.out.println("Metaspace: Auto-expanding, better memory management");
System.out.println("\n3. GARBAGE COLLECTION:");
System.out.println("PermGen: Collected during Full GC, inefficient");
System.out.println("Metaspace: More efficient GC when classes are unloaded");
System.out.println("\n4. OUT OF MEMORY ERRORS:");
System.out.println("PermGen: java.lang.OutOfMemoryError: PermGen space");
System.out.println("Metaspace: java.lang.OutOfMemoryError: Metaspace");
System.out.println("\n5. DEFAULT SIZES:");
System.out.println("PermGen: Platform dependent (32MB-96MB)");
System.out.println("Metaspace: Limited by system memory");
}
}
// Content stored in both:
public static class StoredContent {
// Both PermGen and Metaspace store:
// Class metadata
private static class ExampleClass {
private int instanceField;
private static String staticField = "Static value";
}
// Method metadata
public void exampleMethod(String param) {
// Method bytecode and metadata stored here
System.out.println(param);
}
// Constant pool
private final String CONSTANT_STRING = "Constant pool entry";
// Annotations
@Deprecated
public void deprecatedMethod() {}
// Field metadata
private String instanceFieldMetadata;
}
}

5. Monitoring and Troubleshooting

public class MemoryMonitoring {
public static void monitorMemoryAreas() {
System.out.println("=== Memory Area Monitoring ===");
// Heap memory info
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
System.out.printf("Heap - Max: %dMB, Total: %dMB, Free: %dMB%n",
maxMemory / (1024 * 1024),
totalMemory / (1024 * 1024),
freeMemory / (1024 * 1024));
// Class loading info (indirect Metaspace monitoring)
java.lang.management.ClassLoadingMXBean clBean = 
java.lang.management.ManagementFactory.getClassLoadingMXBean();
System.out.printf("Classes - Loaded: %d, Total Loaded: %d, Unloaded: %d%n",
clBean.getLoadedClassCount(),
clBean.getTotalLoadedClassCount(),
clBean.getUnloadedClassCount());
}
// Simulate Metaspace issues
public static class MetaspaceIssueSimulator {
private static List<Class<?>> loadedClasses = new ArrayList<>();
public static void simulateMetaspaceLeak() {
// This would require bytecode generation in real scenario
// Simulating by loading many classes
for (int i = 0; i < 10000; i++) {
try {
// Load system classes (in real scenario, generate dynamic classes)
Class<?> stringClass = Class.forName("java.lang.String");
loadedClasses.add(stringClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
// JVM Flags for monitoring:
// -XX:+PrintFlagsFinal          // Show all JVM flags
// -XX:+PrintGCDetails           // Detailed GC logging
// -XX:+PrintClassHistogram      // Class histogram before full GC
// -Xlog:gc*                     // Comprehensive GC logging (Java 9+)

6. Common Issues and Solutions

public class CommonIssues {
// PermGen Issues (Java 7 and earlier)
public static class PermGenIssues {
// Common causes of PermGen errors:
// 1. Excessive class loading
public void excessiveClassLoading() {
// Hot deployment without proper cleanup
// Multiple redeploys in application servers
}
// 2. String intern abuse
public void stringInternIssues() {
String largeString = "Very large string value...";
// Calling intern() too frequently can fill PermGen
largeString = largeString.intern();
}
// 3. Dynamic class generation
public void dynamicClassGeneration() {
// Libraries like ASM, CGLIB generating many classes
// Without proper cleanup
}
}
// Metaspace Issues (Java 8+)
public static class MetaspaceIssues {
// Common causes of Metaspace errors:
// 1. Memory leaks from classloaders
public void classLoaderLeaks() {
// Custom classloaders not being garbage collected
// Holding references to generated classes
}
// 2. Excessive dynamic proxy generation
public void proxyGeneration() {
// Frameworks generating many dynamic proxies
// Spring AOP, Hibernate, etc.
}
// 3. Large applications with many dependencies
public void largeApplication() {
// Microservices with numerous libraries
// Each library contributing to Metaspace usage
}
}
// Solutions for both
public static class Solutions {
public void generalSolutions() {
// 1. Monitor memory usage
monitorMemoryAreas();
// 2. Set appropriate memory limits
// For PermGen: -XX:MaxPermSize=256m
// For Metaspace: -XX:MaxMetaspaceSize=512m
// 3. Use application server features properly
// Proper undeployment of applications
// 4. Avoid memory leaks in custom classloaders
cleanupClassLoaders();
}
private void monitorMemoryAreas() {
// Regular monitoring implementation
MemoryMonitoring.monitorMemoryAreas();
}
private void cleanupClassLoaders() {
// Ensure classloaders can be garbage collected
// Don't hold static references to classes from custom classloaders
}
}
}

7. Practical Examples and Best Practices

public class BestPractices {
// Application with good memory practices
public static class WellBehavedApplication {
private static final Logger logger = LoggerFactory.getLogger(WellBehavedApplication.class);
// Use weak references for caching classes
private final WeakHashMap<String, Class<?>> classCache = new WeakHashMap<>();
public void loadClassSafely(String className) {
try {
Class<?> loadedClass = Class.forName(className);
// Cache weakly - allows GC when needed
classCache.put(className, loadedClass);
} catch (ClassNotFoundException e) {
logger.error("Class not found: {}", className, e);
}
}
public void cleanup() {
// Explicit cleanup when possible
classCache.clear();
}
}
// Demonstrating proper classloader usage
public static class ProperClassLoaderUsage {
public static void demonstrateIsolation() {
// Custom classloader for isolation
ClassLoader customLoader = new CustomClassLoader();
try {
// Load classes through custom classloader
Class<?> isolatedClass = customLoader.loadClass("com.example.SomeClass");
// When done, allow classloader to be GC'd
customLoader = null;
System.gc(); // Suggest GC
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
static class CustomClassLoader extends ClassLoader {
// Custom classloader implementation
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// Custom loading logic
return super.loadClass(name);
}
}
}
// Recommended JVM configurations for different scenarios:
public class JVMConfigurations {
// Development environment
public static class Development {
// -Xms512m -Xmx1024m 
// -XX:MaxMetaspaceSize=256m
// -XX:+UseG1GC
}
// Production environment - web application
public static class ProductionWebApp {
// -Xms2g -Xmx4g
// -XX:MaxMetaspaceSize=512m
// -XX:+UseG1GC
// -XX:MaxGCPauseMillis=200
}
// Production environment - microservice
public static class ProductionMicroservice {
// -Xms512m -Xmx512m  // Fixed heap for container
// -XX:MaxMetaspaceSize=256m
// -XX:+UseSerialGC   // For small heaps
}
}

8. Migration from Java 7 to Java 8+

public class MigrationGuide {
public static void migrateFromPermGenToMetaspace() {
System.out.println("=== Migration from Java 7 to Java 8+ ===");
System.out.println("\nBEFORE (Java 7):");
System.out.println("-XX:PermSize=64m");
System.out.println("-XX:MaxPermSize=256m");
System.out.println("-XX:+CMSClassUnloadingEnabled");
System.out.println("\nAFTER (Java 8+):");
System.out.println("-XX:MetaspaceSize=64m");
System.out.println("-XX:MaxMetaspaceSize=256m");
System.out.println("// CMSClassUnloadingEnabled not needed");
System.out.println("\nNOTES:");
System.out.println("1. Remove all PermSize/MaxPermSize flags");
System.out.println("2. Replace with MetaspaceSize/MaxMetaspaceSize if needed");
System.out.println("3. Monitor Metaspace usage in production");
System.out.println("4. Most applications work fine with defaults");
}
// Common migration issues and solutions
public static class MigrationIssues {
public void handleCommonProblems() {
// Issue 1: OutOfMemoryError after migration
// Solution: Increase MaxMetaspaceSize or find memory leaks
// Issue 2: Performance regression
// Solution: Tune GC settings, monitor class loading
// Issue 3: Tools compatibility
// Solution: Update monitoring tools to understand Metaspace
}
}
}

Key Takeaways

  1. PermGen was part of heap memory, Metaspace uses native memory
  2. Metaspace auto-grows and has better memory management
  3. Metaspace garbage collection is more efficient
  4. Both store class metadata, but Metaspace handles it more effectively
  5. Migration from Java 7 to 8+ requires removing PermGen flags
  6. Monitor Metaspace usage in production environments
  7. Use appropriate JVM flags based on your application's needs

The move from PermGen to Metaspace in Java 8 was a significant improvement that eliminated many memory management issues and provided better performance for modern applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper