The ThreadLocal mechanism effectively stores on the current thread a WeakHashMap of ThreadLocal instances to values. Consequently, if the ThreadLocal instance never becomes weakly referenceable, then the entry is effectively leaked.
There are two cases to consider. For the simplicity of discussion, let's assume that ThreadLocal actually stores a WeakHashMap on Thread.currentThread(); in reality, it uses a more sophisticated mechanism that has an equivalent effect.
First consider the "new Weakling" scenario:
- On the first iteration of the loop:
- the Weakling class is loaded from the system class loader
- the Weakling constructor is called
- the Weakling.local static variable is set from null to a new ThreadLocal instance #1
- the ThreadLocal WeakHashMap is updated to store the new Weakling instance #1
- On all subsequent iterations of the loop:
- the Weakling class is already loaded from the system class loader
- the Weakling constructor is called
- the Weakling.local static variable is set from the old ThreadLocal instance #1 to a new ThreadLocal instance #2. The old ThreadLocal instance #1 is now only (weakly) referenced by the WeakHashMap.
- the ThreadLocal WeakHashMap is updated to store the new Weakling instance. During this operation, the WeakHashMap notices that the old ThreadLocal instance #1 is only weakly referenceable, so it removes the [ThreadLocal instance #1, Weakling #1] entry from the Map before it adds the [ThreadLocal instance #2, Weakling #2] entry.
Second consider the "new URLClassLoader(...).loadClass(...).newInstance()" scenario:
- On the first iteration of the loop:
- the Weakling class #1 is loaded from URLClassLoader #1
- the Weakling constructor is called
- the Weakling.local #1 static variable is set from null to a new ThreadLocal instance #1
- the ThreadLocal WeakHashMap is updated to store the new Weakling instance #1
- On all subsequent iterations of the loop
- the Weakling class #n is loaded from URLClassLoader #n
- the Weakling constructor is called
- the Weakling.local #n static variable is set from null to a new ThreadLocal instance #n
- the ThreadLocal WeakHashMap is updated to store the new Weakling instance.
Note that during this final step, ThreadLocal instance #1 is not weakly referenceable. This is because of the following reference chain:
- WeakHashMap value strongly references Weakling instance #1
- Weakling instance #1 strongly references Weakling class #1 via Object.getClass()
- Weakling class #1 strongly references ThreadLocal instance #1 via the static class variable
As long as the loop continues to run, more entries are added to the ThreadLocal WeakHashMap, and the strong reference chain from value-to-key (Weakling instance to ThreadLocal) in the WeakHashMap prevents garbage collection of otherwise stale entries.
I've modified the Loader program to iterate 3 times and then wait for user input. Then, I generated a heap dump using java -Xrunhprof:heap=dump and ctrl-pause/break. The following is my analysis of the final heap dump:
First, there are three Weakling objects:
OBJ 500002a1 (sz=16, trace=300345, class=Weakling@50000296)
OBJ 500003a4 (sz=16, trace=300348, class=Weakling@5000039d)
OBJ 500003e0 (sz=16, trace=300342, class=Weakling@500003d9)
Note that all three Weakling instances (500002a1, 500003a4, and 500003e0) are created from three distinct class instances (50000296, 5000039d, and 500003d9, respectively). Looking at the first object, we can see that it is held as a value in an entry object in the threadLocal map:
OBJ 500002a5 (sz=32, trace=300012, class=java.lang.ThreadLocal$ThreadLocalMap$Entry@5000014b)
referent 500002a4
queue 500009f6
value 500002a1
The referent here is value being held weakly:
OBJ 500002a4 (sz=16, trace=300347, class=java.lang.ThreadLocal@50000125)
Searching, we can see that this object is held as a value in the static
variable "local" of the aforementioned Weakling class:
CLS 50000296 (name=Weakling, trace=300280)
super 50000099
loader 5000017e
domain 50000289
static local 500002a4
static staticRef 500002a1
In conclusion, we have the following strong reference chain loop for this Weakling instance, which prevents it from being garbage collected.
- WeakHashMap value (500002a5) strongly references Weakling instance (500002a1)
- Weakling instance (500002a1) strongly references Weakling class (50000296) via Object.getClass()
- Weakling class (50000296) strongly references ThreadLocal instance (500002a4) via the static class variable
A similar analysis on the other Weakling objects would show a similar result. Allowing the program to run for additional iterations shows that the objects continue to accumulate in this manner.