13

I am referring to this code example, which is being reported in http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6254531

import java.net.URL;

class Loader {
    public static void main(String[] args) throws Exception {
        for (;;) {
            System.gc();
            System.out.print(".");
            System.out.flush();
            new java.net.URLClassLoader(
                new URL[] { new java.io.File(".").toURL() },
                ClassLoader.getSystemClassLoader().getParent()
            ).loadClass("Weakling").newInstance();
        }
    }
}
public class Weakling {
    private static ThreadLocal<Object> local;
    private static Weakling staticRef;
    private Object var = new byte[1000*1000];
    public Weakling() {
        local = new ThreadLocal<Object>();
        local.set(this);
        staticRef = this;
    }

    @Override
    protected void finalize() {
        System.out.print("F");
        System.out.flush();
    }
}

The finalize will never be called. However, if I change the

            new java.net.URLClassLoader(
                new URL[] { new java.io.File(".").toURL() },
                ClassLoader.getSystemClassLoader().getParent()
            ).loadClass("Weakling").newInstance();

to

new Weakling();

It works very well and no leaking detected.

Can anyone explain why the object created by ClassLoader do not have chance to garbage collect itself?

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

1 Answers1

22

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:
    1. the Weakling class is loaded from the system class loader
    2. the Weakling constructor is called
    3. the Weakling.local static variable is set from null to a new ThreadLocal instance #1
    4. the ThreadLocal WeakHashMap is updated to store the new Weakling instance #1
  • On all subsequent iterations of the loop:
    1. the Weakling class is already loaded from the system class loader
    2. the Weakling constructor is called
    3. 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.
    4. 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:
    1. the Weakling class #1 is loaded from URLClassLoader #1
    2. the Weakling constructor is called
    3. the Weakling.local #1 static variable is set from null to a new ThreadLocal instance #1
    4. the ThreadLocal WeakHashMap is updated to store the new Weakling instance #1
  • On all subsequent iterations of the loop
    1. the Weakling class #n is loaded from URLClassLoader #n
    2. the Weakling constructor is called
    3. the Weakling.local #n static variable is set from null to a new ThreadLocal instance #n
    4. 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.

Brett Kail
  • 33,593
  • 2
  • 85
  • 90
  • Why the strong reference to Weakling instance is still there if I am using URLClassLoader? – Cheok Yan Cheng Aug 22 '10 at 06:10
  • Why does the class hold a strong reference to the ThreadLocal instance? – user207421 Aug 22 '10 at 08:59
  • @Yan: the strong reference to the Weakling instance is there regardless of whether or not you use URLClassLoader. The difference is that when you use "new Weakling", there's only one instance of the Weakling class (the one loaded by the system class loader), so each new instance created removes the strong reference to the ThreadLocal, which allows it to be garbage collected. – Brett Kail Aug 22 '10 at 14:25
  • @EJP: Objects in static variables are strongly reachable from the enclosing class. – Brett Kail Aug 22 '10 at 14:26
  • @bkail: I am getting more confuse. How does each each new instance created removes the strong reference to the ThreadLocal? – Cheok Yan Cheng Aug 22 '10 at 15:53
  • @Yan: In the "new Weakling" scenario, the constructor unconditionally does "local = new ThreadLocal". The value previously stored in the static variable is overwritten. Since there are no more strong references, the old ThreadLocal becomes eligible for garbage collection. – Brett Kail Aug 23 '10 at 17:12
  • But there is also no "local = new java.net.URLClassLoader(...)..newInstance()" – Cheok Yan Cheng Aug 23 '10 at 19:44
  • 1
    The newInstance call is on the Class object. It is the reflection equivalent of "new Weakling". The only difference in the "new Weakling" version is that the same Class/ClassLoader object is used every time. – Brett Kail Aug 23 '10 at 21:49
  • What do you mean "same Class/ClassLoader object is used every time."? A new URLClassLoader instance is created everytime, and a new instance of Weakling is being created every time through newInstance. No one is referenced to Weakling instance created by URLClassLoadeder. I do not see how it is different from "new Weakling" – Cheok Yan Cheng Aug 25 '10 at 01:01
  • 1
    When "new Weakling" is used, the Weakling class loaded by the system class loader is used every time, so the same static variable is overwritten repeatedly. When "new URLClassLoader().loadClass().newInstance" is used, a new class loader is created, a new class is loaded, and a new static variable is initialized from null to an instance of the ThreadLocal. – Brett Kail Aug 25 '10 at 02:51
  • 1
    "and a new static variable is initialized...." : So, are you trying to say, in an appplication, there will be N static "local" variables, after N iteration of loop, if I am using "new URLClassLoader().loadClass().newInstance"? – Cheok Yan Cheng Aug 25 '10 at 03:45
  • 1
    Yes. There will be N class loaders, N "Weakling" classes, and N static "local" variables in those "Weakling" classes. None of the N classes will be garbage collected, because of the reference loop: (Weakling.local) -> (ThreadLocal instance) => (Weakling instance) -> (Weakling class) -> (Weakling.local). The "=>" indicates that the ThreadLocal instance does not reference Weakling, but keeps it alive because of the WeakReference entry in the thread's ThreadLocal Map. – Brett Kail Aug 25 '10 at 11:45
  • I slowly get what you mean. So, the N class loaders will not be garbage collector (I do not see any reference on them) as long as we are still in main thread? Do you have any concrete reference for your statement? – Cheok Yan Cheng Aug 26 '10 at 02:58
  • Can you also revise your answer? Based on all the Q&A from comment? They are too brief. – Cheok Yan Cheng Aug 26 '10 at 02:58
  • 2
    I've updated the answer and added a concrete example using HProf heap dump output. Let me know if there's some additional detail I can provide. – Brett Kail Aug 26 '10 at 03:59
  • 4
    Wow, very complete answer, nice job. +1 – Tim Stone Aug 26 '10 at 04:06