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

Che*_*eng 13 java

我指的是这个代码示例,该示例在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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

new Weakling();
Run Code Online (Sandbox Code Playgroud)

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

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

Bre*_*ail 22

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)
Run Code Online (Sandbox Code Playgroud)

请注意,所有三个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
Run Code Online (Sandbox Code Playgroud)

这里的指称是弱势地保持价值:

OBJ 500002a4 (sz=16, trace=300347, class=java.lang.ThreadLocal@50000125)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

  • 哇,非常完整的答案,干得好.+1 (4认同)
  • 我已经更新了答案并添加了一个使用HProf堆转储输出的具体示例.如果我能提供一些额外的细节,请告诉我. (2认同)