为什么ClassLoader创建的对象没有机会自动进行垃圾回收

我指的是这个代码示例,该示例在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 local; private static Weakling staticRef; private Object var = new byte[1000*1000]; public Weakling() { local = new ThreadLocal(); local.set(this); staticRef = this; } @Override protected void finalize() { System.out.print("F"); System.out.flush(); } } 

永远不会调用finalize。 但是,如果我改变了

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

 new Weakling(); 

它运行良好,没有检测到泄漏。

任何人都可以解释为什么ClassLoader创建的对象没有机会垃圾收集自己?

ThreadLocal机制有效地将当前线程上的ThreadLocal实例的WeakHashMap存储为值。 因此,如果ThreadLocal实例永远不会变得弱引用,那么该条目将被有效泄露。

有两种情况需要考虑。 为了简单讨论,我们假设ThreadLocal实际上在Thread.currentThread()上存储了WeakHashMap; 实际上,它使用了一种具有同等效果的更复杂的机制。

首先考虑“新Weakling”场景:

  • 在循环的第一次迭代:
    1. Weakling类是从系统类加载器加载的
    2. 调用Weakling构造函数
    3. Weakling.local静态变量从null设置为新的ThreadLocal实例#1
    4. 更新ThreadLocal WeakHashMap以存储新的Weakling实例#1
  • 在循环的所有后续迭代中:
    1. Weakling类已经从系统类加载器加载
    2. 调用Weakling构造函数
    3. Weakling.local静态变量从旧的ThreadLocal实例#1设置为新的ThreadLocal实例#2。 现在,旧的ThreadLocal实例#1仅被WeakHashMap(弱)引用。
    4. 更新ThreadLocal WeakHashMap以存储新的Weakling实例。 在此操作期间,WeakHashMap注意到旧的ThreadLocal实例#1仅具有弱引用性,因此它在添加[ThreadLocal实例#2,Weakling#2]之前从Map中删除[ThreadLocal实例#1,Weakling#1]条目。进入。

其次考虑“new URLClassLoader(…)。loadClass(…)。newInstance()”场景:

  • 在循环的第一次迭代:
    1. Weakling类#1从URLClassLoader#1加载
    2. 调用Weakling构造函数
    3. Weakling.local#1静态变量从null设置为新的ThreadLocal实例#1
    4. 更新ThreadLocal WeakHashMap以存储新的Weakling实例#1
  • 在循环的所有后续迭代中
    1. Weakling类#n从URLClassLoader #n加载
    2. 调用Weakling构造函数
    3. Weakling.local #n静态变量从null设置为新的ThreadLocal实例#n
    4. 更新ThreadLocal WeakHashMap以存储新的Weakling实例。

请注意,在此最后一步中,ThreadLocal实例#1 不是弱引用的。 这是因为以下参考链:

  • WeakHashMap值强烈引用Weakling实例#1
  • 弱实例#1通过Object.getClass()强引用Weakling类#1
  • 弱类#1通过静态类变量强引用ThreadLocal实例#1

只要循环继续运行,就会向ThreadLocal WeakHashMap添加更多条目,并且WeakHashMap中从值到密钥(Weakling实例到ThreadLocal)的强引用链可以防止对其他过时条目进行垃圾回收。

我已经修改了Loader程序迭代3次然后等待用户输入。 然后,我使用java -Xrunhprof生成堆转储:heap = dump和ctrl-pause / break。 以下是我对最终堆转储的分析:

首先,有三个Weakling对象:

 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) 

请注意,所有三个Weakling实例(500002a1,500003a4和500003e0)都是从三个不同的类实例(分别为50000296,5000039d和500003d9)创建的。 查看第一个对象,我们可以看到它在threadLocal映射中的条目对象中保存为一个值:

 OBJ 500002a5 (sz=32, trace=300012, class=java.lang.ThreadLocal$ThreadLocalMap$Entry@5000014b) referent 500002a4 queue 500009f6 value 500002a1 

这里的指称是价值被弱化:

 OBJ 500002a4 (sz=16, trace=300347, class=java.lang.ThreadLocal@50000125) 

在搜索中,我们可以看到这个对象被保存为上述Weakling类的静态变量“local”中的值:

 CLS 50000296 (name=Weakling, trace=300280) super 50000099 loader 5000017e domain 50000289 static local 500002a4 static staticRef 500002a1 

总之,我们为这个Weakling实例提供了以下强引用链循环,它可以防止它被垃圾回收。

  • WeakHashMap值(500002a5)强引用Weakling实例(500002a1)
  • 弱实例(500002a1)通过Object.getClass()强引用Weakling类(50000296)
  • Weakling类(50000296)通过静态类变量强引用ThreadLocal实例(500002a4)

对其他Weakling对象的类似分析将显示类似的结果。 允许程序运行以进行其他迭代表明对象继续以这种方式累积。