WeakReference cannot be GC'ed even no referent exists.

yumin qi yumin.qi at gmail.com
Thu Apr 28 22:57:04 UTC 2016


Hi, Runtime and GC team

  My last email with a typo on hotspot-gc-dev. So send again, sorry if you
receive multiple same content emails from me --- please add my email in
your reply since I could not see the email I sent out to open alias.

   As stated in the subject, found some weakly referenced object not GC'ed.


  Code here is the test case we used to check for this problem.

  There are two classes AAA and AAB which are all loaded by the
TestClassLoader but different instances which are independent.
TestClassLoader extends from URLClassLoader.

  AAA has a field type of AAB which will be set to null in the test.

public class AAA {
private AAB aab;
public AAA() {
aab = new AAB();
}
public void clear() {
aab = null;
}
}

public class AAB {
 }

The two class have to be in different jars(here AAA.jar contains AAA.class,
and AAB.jar contains AAB.jar), which are not in the class path so are being
loaded from different locations for the test.

import java.net.URL;
import java.net.URLClassLoader;
import java.util.WeakHashMap;


public class TestLoader extends URLClassLoader {
        public static WeakHashMap<TestLoader,Object> map=new
WeakHashMap<TestLoader,Object>();
        private static int count=0;
        public TestLoader(URL[] urls){
                super(urls);
                map.put(this, new Object());
        }
        @SuppressWarnings("resource")
        public Class<?> loadClass(String name) throws
ClassNotFoundException {
                if (name.equals("AAB") && count==0) {
                        try {
                            count=1;
                            URL[] urls = new URL[1];
                            urls[0] = new
URL("file:///home/nijiaben/tmp/AAB.jar"); // You need to use your own
location for AAB.jar here!!!
                            return new TestLoader(urls).loadClass("AAB");
                        } catch (Exception e){
                            e.printStackTrace();
                        }
                } else {
                        return super.loadClass(name);
                }
                return null;
        }
}

TestLoader puts itself in the WeakHashMap --- upon "AAB" loading, it uses
new instance of TestLoader. the new Object is just a object as value which
has nothing to strongly or weakly to the key.

  TTest.java is the test program to start with, basically self explained.

import java.lang.reflect.Method;
import java.net.URL;

// Author: Jiapeng Li

public class TTest {
    private Object aaa;
    public static void main(String args[]){
        try {
            TTest tt = new TTest();
            //Move tt to old gen and clear aab in aaa
            test(tt);
            // do a final full GC, the TestLoader for aab should be purged.
            System.gc();
            System.out.println("finished");  // stop here in debugger and
dump heap
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @SuppressWarnings("resource")
        public static void test(TTest tt){
        try {
            // New instance of TestLoader which will load AAA from AAA.jar
            URL[] urls = new URL[1];
            urls[0] = new URL("file:///home/nijiaben/tmp/AAA.jar");  // You
have to use your own location for AAA.jar here!!!
            tt.aaa=new TestLoader(urls).loadClass("AAA").newInstance();
            // young GC will not purge the class loader, after 10 times of
full GC, it should move it to old gen
            for (int i = 0; i < 10; i++) {
                System.gc();
                Thread.sleep(1000);
            }
            //  set aaa.aab= null,so next full gc will collect  it
            Method[] methods=tt.aaa.getClass().getDeclaredMethods();
            for (Method m : methods){
                if (m.getName().equals("clear")) {
                        m.invoke(tt.aaa);
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  After final  full GC, there should be no instance of AAB exists, but the
instance of AAB's class loader (TestLoader) still not cleaned by GC. See
the stop point above, we dumped heap after final full GC. Following is the
graph for reference which still holds for the WeakHashMap.

[image: Inline image 1]

  Notice that the dependency records the two instances of TestLoaders in
order that they are related,   When AAA is loaded, it loads AAB and after
AAB loaded, dependency records the relationship in _dependencies.

but we don't have 'remove' method to disengage them for GC.
When GC, _dependencies.oops_do, the is_alive Closure still marks the
TestLoader for AAB alive even though it has nothing to refer to due to the
dependency.

void ClassLoaderData::oops_do(OopClosure* f, KlassClosure* klass_closure,
bool must_claim) {
  if (must_claim && !claim()) {
    return;
  }

  f->do_oop(&_class_loader);
  _dependencies.oops_do(f);
  _handles->oops_do(f);
  if (klass_closure != NULL) {
    classes_do(klass_closure);
  }
}

Is this a bug or a design consideration?

Any comments are appreciated!

Thanks
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/hotspot-gc-dev/attachments/20160428/d871ab5a/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image.png
Type: image/png
Size: 60847 bytes
Desc: not available
URL: <http://mail.openjdk.java.net/pipermail/hotspot-gc-dev/attachments/20160428/d871ab5a/image-0001.png>


More information about the hotspot-gc-dev mailing list