Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the object created by ClassLoader do not have chance to garbage collect itself

Tags:

java

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?

like image 486
Cheok Yan Cheng Avatar asked Aug 22 '10 04:08

Cheok Yan Cheng


1 Answers

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.

like image 97
Brett Kail Avatar answered Oct 12 '22 11:10

Brett Kail