05 February 2015

Tags: java |

blog_post_image

I’m sure that a lot of Java developers experienced java.lang.OutOfMemoryError: PermGen space (OOME PermGen). It was very common to get this error after multiple WAR re-deploys on Tomcat v.6.x. Permanent generation (PermGen) region of memory is used to store the internal representation of loaded classes (and much more, see here 1 and 2).

So, we can get OOME PermGen when ClassLoader whats to store class definition, but there is not enough space in PermGen - i.e. loaded too many classes.

Based on this OOME PermGen error can be generated via:

I highly recommend to read What is a PermGen leak? post to get more info about this issue.

Disclaimer

The current post is totally based on How (not) to create a permgen leak?

Generate PermGen leak

The main idea is dynamically create a lot of classes via byte code manipulation library. We are going to use Javassist as it’s the simplest library with nice API.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>my-app</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <skipTests>true</skipTests>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.15.0-GA</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>appassembler-maven-plugin</artifactId>
        <version>1.9</version>
        <configuration>
          <extraJvmArguments>-XX:PermSize=2M -XX:MaxPermSize=4M</extraJvmArguments>
          <programs>
            <program>
              <mainClass>com.mycompany.app.App</mainClass>
              <id>app</id>
            </program>
          </programs>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

–XX:PermSize - Set initial PermGen Size

–XX:MaxPermSize - Set the maximum PermGen Size

See additional comments in the next code snippet

package com.mycompany.app;

import javassist.CannotCompileException;
import javassist.ClassPool;

import java.util.concurrent.TimeUnit;

/**
 * Steps to build and run demo application: <br />
 *
 * <li>mvn clean package appassembler:assemble
 * <li>target/appassembler/bin/app
 *
 */
public class App {
    /**
     * We should use static block for OutOfMemoryError "initialization"
     * It's very important to have it. In other case JVM won't be able to
     * throw (actually create new OutOfMemoryError) this exception because
     * there will be no free memory for this. That's why we creating it beforehand.
     * As you can see we intentionally added output to highlight that THIS error
     * was produces by us.
     */
    static {
        new OutOfMemoryError().printStackTrace();
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("=====================");
            System.out.println("Initialized/created OutOfMemoryError");
            System.out.println("=====================");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("Start dynamic class creation.....\n");
        for (int i = 0; i < 1000; i++) {
            Class clazz = createClass("MyClass" + i);
            // we use this output as indicator to see the rough number of created classes
            // it's not necessary to print every single (just created) class
            if(i % 50 == 0) System.out.println(clazz);
        }
    }

    private static Class createClass(String className) throws CannotCompileException {
        ClassPool pool = ClassPool.getDefault();
        return pool.makeClass(className).toClass();
    }
}
$ mvn clean package appassembler:assemble
$ target/appassembler/bin/app
java.lang.OutOfMemoryError
	at com.mycompany.app.App.<clinit>(App.java:15)
=====================
Initialized/created OutOfMemoryError
=====================
Start dynamic class creation.....

class MyClass0
class MyClass50
class MyClass100
class MyClass150
class MyClass200
class MyClass250
class MyClass300
class MyClass350
class MyClass400
class MyClass450
class MyClass500
class MyClass550
Exception in thread "Reference Handler" java.lang.OutOfMemoryError: PermGen space
	at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:140)
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
	at javassist.ClassPool.toClass(ClassPool.java:1089)
	at javassist.ClassPool.toClass(ClassPool.java:1032)
	at javassist.ClassPool.toClass(ClassPool.java:990)
	at javassist.CtClass.toClass(CtClass.java:1125)
	at com.mycompany.app.App.createClass(App.java:36)
	at com.mycompany.app.App.main(App.java:29)

The next command runs under the hood:
java -XX:PermSize=2M -XX:MaxPermSize=4M -classpath "$CLASSPATH" com.mycompany.app.App

As you can see it was possible to create about 550 MyClass classes before we’ve got an expected error.

Tomcat Leaks

Tomcat team created a nice wiki page where listed and shown the situations where leaks can be detected and fixed.

References


comments powered by Disqus