[groovy-user] GroovyShell evaluate and permgen errors

45 views
Skip to first unread message

Steve Amerige

unread,
Aug 11, 2014, 4:23:48 PM8/11/14
to Groovy Users
Hi all,

We have code using Groovy 2.1.3 as well as Groovy 2.3.6:
import java.lang.management.*

class Test extends Binding
{
    final GroovyShell shell
    Test() { shell = new GroovyShell(this) }
    
    def execute()
    {
        a = 1
        b = 5
        c = 6
        MemoryMXBean mb = ManagementFactory.getMemoryMXBean()
        100.times {
            def val = eval('a + b + c + a*b*c')  // 42, of course
            System.gc()
            if (it % 10 == 0) println mb.getNonHeapMemoryUsage().getCommitted()
        }
    }
    
    def eval(String script) { return shell.evaluate(script) }
    
    static void main(String[] args)
    {
        10.times {
            Test t = new Test()
            t.execute()
        }
    }
}
We've looked at the discussion:

    Groovy objects/classes not GCed after GroovyShell.evaluate()

We're hitting permgen errors I'm wondering if I've hit the same issue.  What do you think?

Thanks,
Steve Amerige
Principal Software Developer, Fraud and Compliance Solutions Development
SAS Institute, 940 NW Cary Pkwy #129, Cary, NC 27513-2792

Steve Amerige

unread,
Aug 11, 2014, 5:44:13 PM8/11/14
to Groovy Users
As an optimization, I've updated the eval method:
def eval(String script) { return this.hasVariable(script) ? this."$script" : shell.evaluate(script) }
In our case, it turned out that many calls to eval were nothing more than scripts to get a variable from the binding.
  • For example, consider eval('x').  In this case, there is no need to do the more problematic shell.evaluate('x') because when x is a variable, then this.x is all that is needed.
When I made the above change, it got us past the permgen errors.  Of course, this really doesn't solve the problem, so I'm still hoping there is a solution that avoids the permgen errors.


Thanks,
Steve Amerige
Principal Software Developer, Fraud and Compliance Solutions Development
SAS Institute, 940 NW Cary Pkwy #129, Cary, NC 27513-2792


Jim White

unread,
Aug 11, 2014, 7:13:26 PM8/11/14
to us...@groovy.codehaus.org
The problem of running out of memory due to the generation of an indefinite number of classes has no general solution AFAICT.  The fundamental problem is that the JVM uses GC and classloaders have no standard means to know that you'll never use a class again.  See the issue Craig Andrews raised (https://github.com/groovy/groovy-core/pull/382) and the related discussion on this list.

There are numerous possible workarounds such as the one you've added.  I suspect if you added a further bit of logic to compile scripts and cache the resulting classes rather than calling evaluate directly then your problem would be mostly managed.

Jim

Steve Amerige

unread,
Aug 12, 2014, 8:02:06 AM8/12/14
to us...@groovy.codehaus.org
Hi Jim,

Thank you for the info.  I've read all of the pages linked to the link below.  Very interesting read.  So, now I'm trying to thing of workarounds that might help.  In my case, I'm providing library code, so I can't tell what scripts might be fed to my code.  In my case, I have a binding that acts as a context into which data is placed by clients.  Other clients may evaluate code with the binding.  At first blush, I thought that evaluate with a binding would be a nice, simple solution.  My solution is data driven: the client's scripts are in a properties file.  If this weren't the case, I'd just change my code to require the clients pass in a closure that I could then simply execute with the binding.

I'll try to come up with something, but if you or anyone else would like to suggest workarounds, I'd be grateful for your ideas.

Many thanks,

Steve Amerige
Principal Software Developer, Fraud and Compliance Solutions Development
SAS Institute, 940 NW Cary Pkwy #129, Cary, NC 27513-2792



Jochen Theodorou

unread,
Aug 12, 2014, 8:21:01 AM8/12/14
to us...@groovy.codehaus.org
Am 11.08.2014 22:21, schrieb Steve Amerige:
[...]
> We're hitting *permgen *errors I'm wondering if I've hit the same
> issue. What do you think?

on the JVM a the class loader is part of the identity of a class.
Without the loader the class cannot exist.On the other hand, the
classloader is supposed to return the same class for each loadClass
request. As a result the class loaders do have to cache what classes
they define.

GroovyShell spawns one internal loader, but unless you do something, all
classes every defined by it will continue to exist. The loader there is
a GroovyClassLoader, which you can get by shell.getClassLoader(). Now
GroovyClassLoader is actually a forest of sub loaders allowing us to
violate the rules above a bit. It spawns a sub classloader for every
compilation and does not cache these loaders, only their defined
classes. If you call cleanCache() on the GroovyClassLoader, it will then
forget about all those classes and the classes become collectable by the
JVM (as long as they are not referenced elsewhere)

So if you know you will evaluate often you can always do
shell.getClassLoader().cleanCache() after X steps. If you need more fine
control, then you can still clean the cache, but may have to do
something else to keep the classes available.

bye blackdrag

--
Jochen "blackdrag" Theodorou - Groovy Project Tech Lead
blog: http://blackdragsview.blogspot.com/
german groovy discussion newsgroup: de.comp.lang.misc
For Groovy programming sources visit http://groovy-lang.org


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email


Jim White

unread,
Aug 12, 2014, 2:43:35 PM8/12/14
to us...@groovy.codehaus.org
The code for GroovyShell.evaluate is this:

Script script = parse(codeSource);        
script.setBinding(context);
return script.run();

My suggestion was to replace your direct evaluate calls with something that memoizes the compiled scripts so that if the number of distinct scripts is reasonably bounded then your problem would be fixed like this:


def evaluator = new CachingEvaluator()
 
assert evaluator.evaluate("1 + 2 * 3") == 7
assert evaluator.evaluate("1 + 2 * ${3}") == 7
assert evaluator.evaluate(new String("1 + 2 * 3")) == 7
assert evaluator.compiledScriptClasses.size() == 1
 
class CachingEvaluator {
GroovyShell shell = new GroovyShell()
Map<String, Class> compiledScriptClasses = [:]
 
Object evaluate(String scriptSource) {
Class klazz = compiledScriptClasses[scriptSource]
 
if (klazz == null) {
klazz = shell.parse(scriptSource).getClass()
compiledScriptClasses[scriptSource] = klazz
}
 
Script script = (Script) klazz.newInstance()
script.setBinding(shell.getContext())
script.run()
}
}

You could of course add your binding short circuit check too which would speed things further and reduce memory usage.

Clearing the caches or cycling shells as Jochen suggests could also be done but would take a lot more time and generate more garbage (and I seem to recall that not working for some folks).

And as you may have gleaned from the previous discussions about this, the reason that Groovy doesn't do this kind of caching itself is that we can't in general guarantee that the same source always results in the same compiled class because of dependencies, but I'm assuming you're controlling the classpath environment in such a way that if the dependencies change that you refresh the whole shebang (or at least create a new shell).

Jim


         
        
Reply all
Reply to author
Forward
0 new messages