WeakReference字符串为什么没有被垃圾回收?为什么会这样?

5

我正在阅读维基百科关于WeakReference的内容,看到了这段代码

public class ReferenceTest {
        public static void main(String[] args) throws InterruptedException {

            WeakReference r = new WeakReference(new String("I'm here"));
            WeakReference sr = new WeakReference("I'm here");

            System.out.println("before gc: r=" + r.get() + ", static=" + sr.get());
            System.gc();
            Thread.sleep(100);

            // only r.get() becomes null
            System.out.println("after gc: r=" + r.get() + ", static=" + sr.get());

        }
}

当运行代码时,结果如下:

垃圾回收前:r=我在这里,static=我在这里

垃圾回收后:r=null,static=我在这里

srr变量都引用了字符串对象。现在r已经被垃圾回收了,但是为什么调用垃圾回收后sr没有被回收呢?

我只是好奇这是怎么发生的。

3个回答

8

这并不是因为字符串池的原因。

真正的原因是ReferenceTest类对表示"I'm here"字面值的字符串对象有一个隐式的硬引用。这个硬引用意味着sr中的弱引用不会被垃圾回收器打破1

实际上:

  • 即使与字面值对应的字符串对象没有被池化,隐式引用也是必要的。(它们确实被池化了...JLS有效地要求了这一点...但我想说的是,即使它们没有被池化,引用也是必要的。否则,Java将在每次计算字符串字面值表达式时创建和“intern”新的String对象,那将是非常低效的!)

  • 字符串池内部使用一种弱引用形式......这样未引用的池化字符串可以被垃圾回收。否则,调用String.intern()可能会成为无法治愈的内存泄漏。

总之,如果你仔细构造一个字符串而没有使用字符串字面值,并将其intern,就像这样:

    char[] chars = {'a', 'b', 'c'};
    WeakReference r = new WeakReference(new String(chars).intern());

如果使用弱引用,你应该会发现(最终)弱引用被释放了。(虽然可能需要几个GC周期。)


1 - 理论上,导致类未加载和垃圾回收可以消除对该字符串文字的最后一个可达硬引用。但是,在这种情况下,如果发生这种情况,您将超过观察WeakReference对象状态的点。至少,在您的示例代码中是这样。此外,除非使用自定义类加载器启动JVM,否则我认为不可能导致入口点类的卸载。这不是容易或有用的事情。


1
这是由于字符串池的原因。当您使用new运算符创建字符串时,它将在池中创建并可供普通垃圾回收使用。但是,当您将字符串定义为文字时,它将在字符串池中创建,并且不会随着常规GC操作而被垃圾回收。

如果我在池中分配了一个字符串,那么这个字符串会在程序终止后被销毁吗? - user948620
是的...当您的程序终止后,分配的内存肯定会消失。 - Renjith
是的...但这并不是由字符串池引起的。它是由于该类对表示字符串字面值的String对象有一个硬引用所致。 - Stephen C

-1

静态变量只有在类被卸载时才会进行垃圾回收。因此,即使您手动触发垃圾回收,您仍然可以看到静态变量的值被打印出来。


但是 srr 都是静态变量。 - user948620
这并没有解释为什么字符串不会变得弱可达,然后被收集。没有静态变量直接引用任何一个字符串实例。 - Stephen C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接