No subject
yumin qi
yumin.qi at gmail.com
Thu Apr 28 00:01:28 UTC 2016
Hi, Runtime and GC team
(This email has been sent out to hotspot-dev alias, but I did not receive
the notice. Since i already registered to it and did not receive it so I
decided send the copy again to hotspot-rumtime-dev an hotspot-gc-dev).
This is Yumin now have a question to ask for help --- I am not sure if
it is a bug.
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
Yumin
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/hotspot-gc-dev/attachments/20160427/ac502a4e/attachment.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image.png
Type: image/png
Size: 60847 bytes
Desc: not available
URL: <https://mail.openjdk.org/pipermail/hotspot-gc-dev/attachments/20160427/ac502a4e/image.png>
More information about the hotspot-gc-dev
mailing list