【问题标题】:Weakly referenced object won't get garbage collected弱引用对象不会被垃圾回收
【发布时间】:2020-01-19 16:39:45
【问题描述】:

我担心的是一个曾经被强引用的对象的实例,但是在对其强引用显式分配空值和显式调用System.gc() 之后,该实例仍然可以通过弱引用。如果我理解正确,当被引用对象只剩下弱引用时,保证在下一个 GC 会话中清除该引用对象。我错过了什么?

参考代码:

public class References {
    public static void main(String[] args) {

        Example strongReferenceWrappedInWeak = new Example(42);
        strongReferenceWrappedInWeak.printA();

        WeakReference<Example> exampleWeakReference = new WeakReference<>(strongReferenceWrappedInWeak);

        System.gc();

        Example retrievedExample = exampleWeakReference.get();
        retrievedExample.printA(); //this works, because a strong reference is present to the instance, hence it's not cleared

        strongReferenceWrappedInWeak = null; //eligible for garbage collection

        System.gc();

        Example retrievedExampleTwo = exampleWeakReference.get(); //should be null
        retrievedExampleTwo.printA(); //should throw NPE
    }
}

class Example {
    private int a;

    Example(int a) {
        this.a = a;
    }

    void printA() {
        System.out.println(this.a);
    }
}

【问题讨论】:

  • 垃圾收集不是一个可预测的过程。它可能只是在第一次扫描中标记对象,然后在下一次清除。所以任何依赖 GC 进程的行为都是不确定的。
  • 我会把它作为这个问题的答案。谢谢!
  • 更不用说,不能保证System.gc() 会做任何事情。

标签: java reference weak-references


【解决方案1】:

垃圾收集以神秘的方式运作。

在 Java 生态系统中有多种垃圾收集器的实现,它们的行为截然不同。

垃圾收集运行的时间因垃圾收集器的实现而异,也可能取决于 JVM 的当前状况。一个收集器可能几乎连续运行,而另一个可能会等到内存不足。 (为了说明这一点,我在这里过于简单化了。)

是否收集所有垃圾,或者只是收集其中的一部分,也可能因收集器实现和 JVM 状态而异。

System.gc 的调用只是一个建议,而不是一个命令。垃圾收集器可以随意忽略它。

在 Java 中,您不应该在管理内存方面付出很大的努力。现代 JVM 实现在这方面比任何单个程序员都可能做得更好。请确保在使用完对象后释放对对象的所有引用。或者使用WeakReference/SoftReference。然后信任 JVM 和垃圾收集器来完成它的工作。

在极端情况下(非常大的内存或大量的对象流失),您可能需要研究各种垃圾收集器实现的行为。并且可能考虑替代方案,例如来自 Azul Systems 的 Zing 或来自 Oracle 的 GraalVM。但是对于大多数项目来说,通常的基于 OpenJDK 的 JVM 运行良好。

【讨论】:

  • 虽然你所说的一切都是真的(对此表示赞同),但实际上并不是problem
  • 感谢罗勒的详尽解释。我知道我的 JVM 肯定会比我做得更好(在这个小例子中并不重要),显式 System.gc() 调用只是(如你所说)建议JVM把垃圾清理到位,这样我就可以看到这个实验的结果了。我想这些电话几乎总是在现实世界的项目中被省略/多余?干杯!
【解决方案2】:

strongReferenceWrappedInWeak = null 不会使 Example 对象实例符合垃圾回收条件,因为 retrievedExample 仍然保持对它的强引用

要修复,请添加retrievedExample = null;

Example strongReferenceWrappedInWeak = new Example(42);
strongReferenceWrappedInWeak.printA();

WeakReference<Example> exampleWeakReference = new WeakReference<>(strongReferenceWrappedInWeak);

System.gc();

Example retrievedExample = exampleWeakReference.get();
retrievedExample.printA(); //this works, because a strong reference is present to the instance, hence it's not cleared

retrievedExample = null;
strongReferenceWrappedInWeak = null; //now eligible for garbage collection

System.gc();

Example retrievedExampleTwo = exampleWeakReference.get(); //will be null
retrievedExampleTwo.printA(); //will throw NPE

或者,不要使用局部变量创建强引用,只需从弱引用中直接调用方法即可。这样你就不会像你一样不小心留下一个强有力的参考。 *(在printA() 调用期间,this 引用是强引用,因此在调用期间 不能对对象进行GC)*

Example strongReferenceWrappedInWeak = new Example(42);
strongReferenceWrappedInWeak.printA();

WeakReference<Example> exampleWeakReference = new WeakReference<>(strongReferenceWrappedInWeak);

System.gc(); //does not collect object, since strong reference still exists

exampleWeakReference.get().printA(); //works

strongReferenceWrappedInWeak = null; //eligible for garbage collection
System.gc(); //collects object, since it is now weakly referenced only

exampleWeakReference.get().printA(); //throws NPE

输出(来自两者)

42
42
Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:**)

在 Java 13 上测试

【讨论】:

  • IIRC 在 Java Puzzlers 中有一个这样的例子。那个人使用catch 块中的异常来处理奇怪变化的行为。
  • @TomHawtin-tackline 什么“奇怪”的变化行为?
  • 我忽略了创建局部变量的部分,该变量将隐含地获得对所指对象的强引用。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-20
  • 2012-07-23
相关资源
最近更新 更多