We are evaluating GroovyShell interpreter (v2.4) in our application for dynamically executing standard Java syntax.
Earlier, we were using Java BeanShell Interpreter for it, but it has an issue under high load which prompted us to look for an alternative such as Groovy.
Sample Java Code
static String script = "int y = x * x; System.out.println(\"** value of y ** :: \" + y ); ";
GroovyShell gs = new GroovyShell();
Script evalScript = gs.parse("void evalMethod() {" + script + "}");
// bind variables
Binding binding = new Binding();
binding.setVariable("x", 5);
evalScript.setBinding(binding);
// invoke eval method
evalScript.invokeMethod("evalMethod", null);
We are seeing thread lock contention when multiple threads execute above code simultaneously. We have multiple threads in the blocked state which is degrading application performance.
Blocked Thread Call Stack
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
- waiting to lock <0x00000007bc425e40> (a java.lang.Object)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:411)
- locked <0x00000007bd369ba8> (a groovy.lang.GroovyClassLoader)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:677)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:545)
at org.codehaus.groovy.control.ClassNodeResolver.tryAsLoaderClassOrScript(ClassNodeResolver.java:185)
at org.codehaus.groovy.control.ClassNodeResolver.findClassNode(ClassNodeResolver.java:170)
at org.codehaus.groovy.control.ClassNodeResolver.resolveName(ClassNodeResolver.java:126)
at org.codehaus.groovy.control.ResolveVisitor.resolveToOuter(ResolveVisitor.java:676)
at org.codehaus.groovy.control.ResolveVisitor.resolve(ResolveVisitor.java:313)
at org.codehaus.groovy.control.ResolveVisitor.transformPropertyExpression(ResolveVisitor.java:845)
at org.codehaus.groovy.control.ResolveVisitor.transform(ResolveVisitor.java:696)
at org.codehaus.groovy.control.ResolveVisitor.transformMethodCallExpression(ResolveVisitor.java:1081)
at org.codehaus.groovy.control.ResolveVisitor.transform(ResolveVisitor.java:702)
at org.codehaus.groovy.ast.ClassCodeExpressionTransformer.visitExpressionStatement(ClassCodeExpressionTransformer.java:142)
at org.codehaus.groovy.ast.stmt.ExpressionStatement.visit(ExpressionStatement.java:42)
at org.codehaus.groovy.ast.CodeVisitorSupport.visitBlockStatement(CodeVisitorSupport.java:37)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitBlockStatement(ClassCodeVisitorSupport.java:166)
at org.codehaus.groovy.control.ResolveVisitor.visitBlockStatement(ResolveVisitor.java:1336)
at org.codehaus.groovy.ast.stmt.BlockStatement.visit(BlockStatement.java:71)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitClassCodeContainer(ClassCodeVisitorSupport.java:104)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitConstructorOrMethod(ClassCodeVisitorSupport.java:115)
at org.codehaus.groovy.ast.ClassCodeExpressionTransformer.visitConstructorOrMethod(ClassCodeExpressionTransformer.java:53)
at org.codehaus.groovy.control.ResolveVisitor.visitConstructorOrMethod(ResolveVisitor.java:201)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitMethod(ClassCodeVisitorSupport.java:126)
at org.codehaus.groovy.ast.ClassNode.visitContents(ClassNode.java:1081)
at org.codehaus.groovy.ast.ClassCodeVisitorSupport.visitClass(ClassCodeVisitorSupport.java:53)
at org.codehaus.groovy.control.ResolveVisitor.visitClass(ResolveVisitor.java:1279)
at org.codehaus.groovy.control.ResolveVisitor.startResolving(ResolveVisitor.java:176)
at org.codehaus.groovy.control.CompilationUnit$12.call(CompilationUnit.java:663)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:943)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:605)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:554)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:298)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:268)
- locked <0x00000007bd372240> (a java.util.HashMap)
at groovy.lang.GroovyShell.parseClass(GroovyShell.java:688)
at groovy.lang.GroovyShell.parse(GroovyShell.java:700)
at groovy.lang.GroovyShell.parse(GroovyShell.java:736)
at groovy.lang.GroovyShell.parse(GroovyShell.java:727)
My questions are:
Script
object. Is Script
object thread-safe? Moreover, I need to bind variables to Script
object (via groovy Binding
object, which is different for each execution in different threads), so I don't think caching groovy Script
object is a viable option.groovy parse&compile is a quite heavy part
if your script is static - then you have to parse it just once
if the scripts always different but repeating - you can cache compiled groovy scripts into
ConcurrentHashMap<String, Class<groovy.lang.Script>>
after
Script evalScript = gs.parse(...);
just put evalScript.getClass()
into cache of compiled scripts.
and before gs.parse(...)
check if you have compiled script and just take a new instance of the cached class if it exists.
String script = "int y = x * x; System.out.println(\"** value of y ** :: \" + y ); ";
def ts = System.currentTimeMillis()
for(int i=0;i<100;i++){
GroovyShell gs = new GroovyShell();
Script evalScript = gs.parse("void evalMethod() {" + script + "}");
// bind variables
Binding binding = new Binding();
binding.setVariable("x", i);
evalScript.setBinding(binding);
// invoke eval method
evalScript.invokeMethod("evalMethod", null);
}
println ">> ${System.currentTimeMillis() - ts} millis"
1165 millis
String script = "int y = x * x; System.out.println(\"** value of y ** :: \" + y ); ";
GroovyShell gs = new GroovyShell();
Class<Script> scriptClass = gs.parse("void evalMethod() {" + script + "}").getClass();
def ts = System.currentTimeMillis()
for(int i=0;i<100;i++){
Script evalScript = scriptClass.newInstance();
// bind variables
Binding binding = new Binding();
binding.setVariable("x", i);
evalScript.setBinding(binding);
// invoke eval method
evalScript.invokeMethod("evalMethod", null);
}
println ">> ${System.currentTimeMillis() - ts} millis"
6 millis