ThreadLocal in Context prevents class unloading

RESOLVED FIXED

Status

Rhino
Core
RESOLVED FIXED
13 years ago
13 years ago

People

(Reporter: Attila Szegedi, Assigned: Igor Bukanov)

Tracking

Details

Attachments

(1 attachment)

(Reporter)

Description

13 years ago
User-Agent:       Opera/7.54 (Windows NT 5.0; U)  [en]
Build Identifier: 

I have an environment where most of the system (including Rhino) is hot-
reloadable. The classes are loaded through a particular java.net.URLClassLoader, 
and upon detecting a change, the class loader is thrown away, and a new one is 
created, allowing for the classes to be reloaded. However, a small issue in 
Rhino's Context class prevents the throwaway class loader (and all the classes 
it loads) from being garbage collected, resulting in memory leak over time.

The issue is the ThreadLocal "Context[] storage". Since the type of the value in 
ThreadLocal is org.mozilla.javascript.Context[], the class org.mozilla.
javascript.Context will remain permanently loaded, and will keep the class 
loader from being garbage collected -- we're utilizing a permanent set of 
threads, actually they're created by JMS sessions, so we can't just terminate 
and restart the threads to get rid of the stuck thread locals. However, there is 
a brilliantly simple solution - store an Object[] instead of Context[] in the 
thread local. Granted, we sacrifice a bit of static type safety, but Rhino will 
no longer prevent the class loader that loaded it from being garbage collected.
Patch on the way.

Reproducible: Always

Steps to Reproduce:
(Reporter)

Comment 1

13 years ago
Created attachment 173365 [details] [diff] [review]
The patch that solves the problem

Patch attached
(Assignee)

Comment 2

13 years ago
I thought that Java used week references to store ThreadLocal in Thread
instances. Apparently it does not which also means that even with the patch
after Rhino reloading all those ThreaLocal instances from reloaded Rhino classes
would stuck in memory forever.

Of cause it is much better then forever stuck Rhino classes themselves so I will
commit the patch after regression testing. 
Status: NEW → ASSIGNED
(Assignee)

Comment 3

13 years ago
I committed the patch. 

BTW, since you repeatedly reload Rhino classes, did the patch address memory
leak completely on your JVM? Or do you still see some leaks like the one
described in 
https://bugzilla.mozilla.org/show_bug.cgi?id=243305#c9 ?
Status: ASSIGNED → RESOLVED
Last Resolved: 13 years ago
Resolution: --- → FIXED
(Reporter)

Comment 4

13 years ago
(In reply to comment #3)
> 
> BTW, since you repeatedly reload Rhino classes, did the patch address memory
> leak completely on your JVM? Or do you still see some leaks like the one
> described in 
> https://bugzilla.mozilla.org/show_bug.cgi?id=243305#c9 ?

I'll run it through a profiler now (been profiling all day :-) ). However, what 
you say is certainly true - the ThreadLocal object and the Object[] will get 
stuck in the thread's local table... Hm... maybe in the end I'll really have to 
introduce a feature for perodically shutting down and recreating the JMS 
sessions...
(Reporter)

Comment 5

13 years ago
(In reply to comment #3)
> I committed the patch. 
> 
> BTW, since you repeatedly reload Rhino classes, did the patch address memory
> leak completely on your JVM? Or do you still see some leaks like the one
> described in 
> https://bugzilla.mozilla.org/show_bug.cgi?id=243305#c9 ?

No, I see no leaks now. As a matter of fact, Thread indeed holds the ThreadLocal 
objects using weak references. However, weak references are defeated if the 
value - directly or indirectly - refers back strongly to the key. In the case 
with storing a Context[], we had a strong reference from value to key via the 
path value = Context[] instance -> Context[].class -> Context.class -> Context.
threadLocalCx = key. Storing an Object[] instead of Context[] solved it for good 
:-). Provided the array element is null (which is easy by using a Context.exit() 
in a finally block), the ThreadLocal object can be fully garbage collected after 
the Context class is unloaded.
(Reporter)

Comment 6

13 years ago
Thinking about it, I believe it'd be necessary to provide a comment around 
"storage = new Object[1];" explaining why do we use it instead of Context[] - 
it's not immediately apparent, and some otherwise well-meaning soul (who comes 
to maintain it sometime in the future after we all moved along) unsuspectingly 
rewrites it back to Context[]...
(Assignee)

Comment 7

13 years ago
(In reply to comment #5)
> As a matter of fact, Thread indeed holds the ThreadLocal 
> objects using weak references. However, weak references are defeated if the 
> value - directly or indirectly - refers back strongly to the key. In the case 
> with storing a Context[], we had a strong reference from value to key via the 
> path value = Context[] instance -> Context[].class -> Context.class -> Context.
> threadLocalCx = key.


But why the reference from Context.threadLocalCx prevents in your case Rhino
classes from garbage collection? If ThreadLocal value is reachable from Thread
only through week reference, it should allow to GC classes as long as the last
strong reference to them and their class loader is gone.
(Reporter)

Comment 8

13 years ago
(In reply to comment #7)
> 
> But why the reference from Context.threadLocalCx prevents in your case Rhino
> classes from garbage collection? If ThreadLocal value is reachable from Thread
> only through week reference, it should allow to GC classes as long as the last
> strong reference to them and their class loader is gone.

No, only the keys in the WeakHashMap are weakly reachable (in our case, the 
ThreadLocal object). The values in WeakHashMap are strongly reachable. And if 
the value is a Context[], it'll establish a strong reference to the ThreadLocal 
key, as outlined in comment #5, preventing it to be GC-ed for the lifetime of 
the thread.
(Assignee)

Comment 9

13 years ago
(In reply to comment #8)
>... only the keys in the WeakHashMap are weakly reachable (in our case, the 
> ThreadLocal object). The values in WeakHashMap are strongly reachable. And if 
> the value is a Context[], it'll establish a strong reference to the ThreadLocal 
> key, as outlined in comment #5, preventing it to be GC-ed for the lifetime of 
> the thread.

I will add then comments about using Object[], not Context[] after I will finish
with bug 281247.
You need to log in before you can comment on or make changes to this bug.