Hello,
I have been investigating some memory problems we've been seeing, having
embedded rhino in our application. The symptom was basically that there
seemed to be a large amount of memory that wasn't being reclaimed after
our rhino script completed.
Agressively paraphrased, our code looks as follows:
static Scriptable sealedSharedScope = /* set this up statically, once */
...
/** Run an ad-hoc script */
void runScript(Context cx, String script)
{
// create a new scope to run the script in.
// As per instructions:
//
https://developer.mozilla.org/En/Rhino_documentation/Scopes_and_Contexts#Sharing_Scopes)
Scriptable newScope = cx.newObject(sealedSharedScope);
newScope.setPrototype(sealedSharedScope);
newScope.setParentScope(null); // #setParentScope
// Create some arbitrary class, convert it to a JS object, and add
it to the newScope.
MyClass javaObj = new MyClass();
Object jsObj = Context.javaToJS(javaObj, newScope);
ScriptableObject.putProperty(newScope, "myObj", jsObj);
// run the script
Object result = cx.evaluateString(newScope, script, "<MyScript>", 1,
null);
}
Using hprof & the Eclipse Java Memory Analyzer, and looking at the rhino
source, I discovered the following:
Internally, Context.javaToJS() caches the names/types of the
fields/methods of the java class that is being wrapped. This speeds up
subsequent wrappings of other instances of this class. However, as I
discovered, it /also/ caches the /scope/ that you pass to
Context.javaToJS(). The following reference chain illustrates this:
#1 '- sharedScope org.mozilla.javascript.NativeObject @ 0x2aaab887c388
'- associatedValues java.util.Hashtable @ 0x2aaab8929518
'- table java.util.Hashtable$Entry[11] @ 0x2aaab8929558
'- [7] java.util.Hashtable$Entry @ 0x2aaab89295c8
#2 '- value org.mozilla.javascript.ClassCache @ 0x2aaab8929608
'- classTable java.util.HashMap @ 0x2aaab8929640
'- table java.util.HashMap$Entry[16] @ 0x2aaab8929680
'- [6] java.util.HashMap$Entry @ 0x2aaab89aa110
#3 '- value org.mozilla.javascript.JavaMembers @
0x2aaab89a37a8
'- members java.util.Hashtable @
0x2aaab89a37f0
'- table java.util.Hashtable$Entry[23]
@ 0x2aaab89a3830
'- [21] java.util.Hashtable$Entry @
0x2aaab89a3900
#4 '- value
org.mozilla.javascript.NativeJavaMethod @ 0x2aaab89a3930
#5 '- parentScopeObject
org.mozilla.javascript.NativeObject @ 0x2aaab8929a08
#1 My static, shared, sealed scope.
#2 The instance of ClassCache (which gets associated with the static,
shared, sealed scope).
#3 The JavaMembers instance which caches MyClass.
#4 One of the NativeJavaMethod instances. One is created for each
method/field of MyClass.
#5 parentScopeObject is a referece to the scope that was passed in to
Context.javaToJS().
Because of this reference chain, any properties of newScope will be
inellible for garbage-collection, even after the script has run, because
newScope continues to be referenced by the instance of ClassCache (the
field that holds the actual reference is
NativeJavaMethod.parentScopeObject).
For example, if the script that I run looks like this:
var arr = new Array();
for (var i=0;i<10000;i++) {
arr.push(new MyObject());
}
then "arr" (and all its contents) will persist in memory /after/ the
script has finished running. In my particular case, I was losing about
100MB of RAM to this cache.
I'm not sure what change (if any) might be made to fix this. The method
where the instance of JavaMembers is actually built:
JavaMembers.lookupClass(Scriptable scope, Class dynamicType, Class
staticType, boolean includeProtected)
does /try/ to use the "top level" scope but it is essentially thwarted
by the following line (from #setParentScope, above).
newScope.setParentScope(null); // #setParentScope
i.e. JavaMembers.lookupClass() can't work out that it needs to use
sharedSealedScope, because newScope has set its parentScope to null.
In my particular situation, I have made a simple modification to fix my
particular problem. I changed the following line in my code:
Object jsObj = Context.javaToJS(javaObj, newScope);
to
Object jsObj = Context.javaToJS(javaObj, sharedSealedScope);
It doesn't matter to me if sharedSealedScope is held onto by ClassCache
because ... it's static - i.e. I'm caching it /anyway/.
This solution might not be suitable in all circumstances. For example,
you might not have sharedSealedScope to-hand. Additionally, you might
want to periodically set sharedSealedScope to a new value (which would
/not/ remove the value cached by ClassCache).
Another option I had was to manually clear the ClassCache, but I was
reluctant to do this for fear of introducing a performance cost.
I hope the above proves useful to others!
Kieran
_______________________________________________
dev-tech-js-engine-rhino mailing list
[email protected]
https://lists.mozilla.org/listinfo/dev-tech-js-engine-rhino