Details
-
Type:
Bug
-
Status:
Open
-
Priority:
Major
-
Resolution: Unresolved
-
Affects Version/s: 1.7.2
-
Fix Version/s: None
-
Component/s: Groovlet / GSP
-
Labels:None
-
Number of attachments :
Description
TemplateServlet by default uses SimpleTemplateEngine. SimpleTemplateEngine uses a cache to store loaded Template objects. These template objects contain a "script" instance variable that corresponds to the compiled gsp script, the classes are named SimpleTemplateScript[x] where x is a growing number. Problem with this cache is that it is a WeakHashMap. This means that when garbage collection occurs, templates will eventually be removed from the cache-map. It was probably intended so that the cache would empty itself in case heap is running out. Problem is that the ClassLoader that loaded the script classes inside the Template-objects is still kept in memory (by TemplateServlet.engine.groovyShell.loader reference) and this prevents the classes from being garbage collected.
After the Template object has been removed from the cache by GC and someone request the corresponding gsp template again, TemplateServlet will have to generate a new Template object and because the script classes in templates are generated by passing in a string to the groovyShell, the GroovyClassLoader doesn't recognize that it has already loaded a similar class. In the end, PermGen fills up from the same template classes being compiled multiple times.
The best fix I've found yet (also the easiest), just use the groovy.text.GStringTemplateEngine instead. It doesn't have this problem. My suggestion for "fixing" the issue would be just to change the default. Maybe even marking SimpleTemplateEngine as "deprecated", unless there are good reasons to use it.
To reproduce the issue:
1.download an empty Tomcat instance (I used apache-tomcat-5.5.27)
2.empty webapps folder
3.to speed up testing, configure tomcat with restricted heap and permgen with following JAVA_OPTS:
JAVA_OPTS="
-verbose:gc
-Xmx64M
-XX:MaxPermSize=30M
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+HeapDumpOnOutOfMemoryError
-Xloggc:gc.log
-XX:HeapDumpPath=dump.hprof
"
4.copy the attached groovlets.war webapp to the webapps folder (expanded or not, no matter). The webapp contains the default groovlet setup with one index.groovy and one index.gsp that index.groovy dispatches to. Both groovy files are very simple they just print "Hello, groovy!" on a webpage many times.
5.start tomcat, open jconsole on tomcat pid (optional).
6.verify installation by calling http://localhost:8080/groovlets/index.groovy you should see "Hello, Groovy multiple times".
7.view perm gen from jconsole.
8.repeat cycle:
a) with your favorite load tester, stress the script (fire multiple threads and multiple http gets).
For example, with Apache Bench: ab -c 30 -n 10000 "http://localhost:8080/groovlets/index.groovy"
b) trigger garbage collection from jconsole (Perform GC button)
c) watch Perm Gen going up slowly
After 5-10 cycles you either get PermGen error to catalina.out or it just doesn't answer anymore.
Looks like I jumped to conclusions too early. Switching to GStringTemplateEngine does prevent the PermGen problem, but I am still getting memory problems. I saw increased load on the server where I've deployed groovlets, heap summary gave me >99.999% of Old Generation in use and the load was coming from FullGC cycles happening. I looked at the heap dump with Eclipse's MAT tool, java.beans.introspector was first in the dominator tree, second was the WebAppClassloader, and inside that hashmaps that kept BeanInfo and some Closurex_Metaclass-objects. I think that with GStringTemplateEngine the classes are actually unloaded so PermGen doesn't fill up, but reflection and other metainformation that Groovy requires is left behind from the classes and fill the heap.
Should it be possible to compile and execute the groovy script multiple (endless) times in a loop endlessly, always throwing away the produced class and it's unique classloader? For me this doesn't seem to work as the heap gets full from introspection caches on other objects containing meta information about the groovy scripts. I could use some help here from someone with more groovy knowledge.