Repeated use of Groovy Shells results in PermGen space full

1k views Asked by At

In an application, I repeatedly create instances of GroovyShell and execute custom scripts on them. When doing this, I experience problems that the perm gen space in Java 6 and Java 7 is filling up and classes are not unloaded. I am using Groovy 2.4.7. The problem can be reproduced with the following code:

import groovy.lang.GroovyShell;

public class Demo {


    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10000000; i++) {
            GroovyShell gs = new GroovyShell();
            Object result = gs.evaluate(" 'Hello, World';");
            assert result.equals("Hello, World");
        }
    }
}

I am using the following VM arguments:

-verbose:class -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+CMSClassUnloadingEnabled -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses -XX:+TraceClassUnloading -XX:MaxPermSize=16M -XX:+UseConcMarkSweepGC -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails -XX:-UseParNewGC

When I execute the test from above, the PermGen is full after a little over 2000 iterations. Also, triggering the GC manually does not work.

Is there any way to force class unloading here or trigger the GC so that the classes are removed from PermGen space?

3

There are 3 answers

1
Nicholas On BEST ANSWER

Woah. Hold up. I just gave this one a quick shot and it seems to work perfectly. Instead of using the shell directly, though, you compile a script and then run the script (for the same outcome). Then call the specified cleaner.

public static void test() {
    for (int i = 0; i < 10000000; i++) {
        GroovyShell gs = new GroovyShell();
        Script script = gs.parse(" 'Hello, World';");
        Object result = script.run();
        assert result.equals("Hello, World");
        clearAllClassInfo(script.getClass());
    }                  
}

public static void clearAllClassInfo(Class<?> type) {
    try {
        Field globalClassValue = ClassInfo.class.getDeclaredField("globalClassValue");
        globalClassValue.setAccessible(true);
        GroovyClassValue classValueBean = (GroovyClassValue) globalClassValue.get(null);
        classValueBean.remove(type);
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }           
}      

Obviously you could optimize the cleaner a bit to eliminate a bunch of reflection.

PermGen is pretty flat: enter image description here

And classes are being unloaded at a brisk pace: enter image description here

Credit to @BilboDai.

1
Brian On

Reuse the GroovyShell instance:

import groovy.lang.GroovyShell;

public class Demo {


    public static void main(String[] args) throws Exception {
        //Create outside the loop
        GroovyShell gs = new GroovyShell();
        for (int i = 0; i < 10000000; i++) {
            Object result = gs.evaluate(" 'Hello, World';");
            assert result.equals("Hello, World");
        }
    }
}
0
Nicholas On

I can empathize since I have been wrestling with this for a while. I implemented this solution, and it improved the lifetime of PermGen in Java 1.6, but did not eliminate the core issue.

For some reason, I do not see the problem in Java 8, so I gave up trying to work around. (Have not tried with Java 7).

Hopefully you can get further than I did.