【问题标题】:Does setting Java objects to null do anything anymore?将 Java 对象设置为 null 有什么作用吗?
【发布时间】:2010-10-25 10:51:26
【问题描述】:

我在浏览一些旧书时发现了 Peter Hagger 的“Practical Java”的副本。在性能部分,建议在不再需要时将对象引用设置为 null

在 Java 中,将对象引用设置为 null 会提高性能还是垃圾回收效率?如果是这样,在什么情况下这是一个问题?容器类?对象构成?匿名内部类?

我经常在代码中看到这一点。这是现在过时的编程建议还是仍然有用?

【问题讨论】:

  • 对其进行分析。在现代运行时,您不应该看到任何显着的性能或内存占用增加。
  • @Jason,个人资料?假设我将分析足够多的案例集以获得足够好的结果集来回答这个问题。而且我没有选择一组 VM 已经优化到足以掩盖 gc 和性能问题的案例。这就是我在这里问这个的原因。了解这是一个问题的案例。

标签: java garbage-collection performance


【解决方案1】:

这在一定程度上取决于您何时考虑将引用归零。

如果您有一个对象链 A->B->C,那么一旦 A 不可访问,A、B 和 C 都将有资格进行垃圾回收(假设没有其他内容涉及 B 或 C)。例如,没有必要也从来没有必要将引用 A->B 或 B->C 显式设置为 null。

除此之外,大多数时候问题并没有真正出现,因为实际上您正在处理集合中的对象。您通常应该始终考虑通过调用适当的 remove() 方法从列表、地图等中删除对象。

过去有一些建议将引用设置为 null 的情况特别是在 长范围内,在该范围内停止使用内存密集型对象。例如:

{
  BigObject obj = ...
  doSomethingWith(obj);
  obj = null;             <-- explicitly set to null
  doSomethingElse();
}

这里的基本原理是,因为 obj 仍在范围内,所以如果没有显式将引用置空,它直到 doSomethingElse() 之后才会变成垃圾回收方法完成。这是可能不再适用于现代 JVM 的建议:事实证明,JIT 编译器可以计算出在什么时候不再使用给定的本地对象引用。

【讨论】:

  • 机器可以优化局部变量。类变量不能。我看到工作线程(在线程池上)在处理请求后没有释放它们的状态对象,因此将这些对象固定在内存中,直到工作线程被赋予一个新任务(它立即用新的状态对象覆盖旧的状态对象)。使用大型状态对象和数百个工作线程(想想:大型 http 服务器),这可能是大量的内存保持和复制,但永远不会再次使用。
  • 我可能应该补充一点:我上面给出的建议是一般要遵循的建议,假设行为良好的库、行为良好的 VM 等。如果你发现你有一个线程在作业完成后挂在对象上的池库,嗯......这是所述线程池库中的一个错误,可能可以通过将对象引用归零来解决,但同样可以通过使用错误较少的线程池来解决图书馆。我不确定这样的错误是否会改变一般设计原则。
【解决方案2】:

不,这不是过时的建议。悬空引用仍然是一个问题,特别是如果您正在使用预分配的数组实现可扩展的数组容器(ArrayList 等)。超出列表“逻辑”大小的元素应该被清空,否则它们不会被释放。

参见 Effective Java 第 2 版,第 6 条:消除过时的对象引用。

【讨论】:

  • 这是语言问题还是虚拟机实现问题?
  • 这是一个“语义”问题。基本上,因为您已经预先分配了一个数组,VM 会看到这一点。它对容器的“逻辑”大小一无所知。假设您有一个大小为 10 的 ArrayList,由一个 16 元素数组支持。 VM 无法知道项目 10..15 并未实际使用;如果这些插槽有内容,它们将不会被释放。
  • 容器类之外呢?在对象组合中,内部对象没有被外部对象释放。
  • @sal 一般的经验法则是,如果你不能引用它,它就会被垃圾回收。因此,如果外部对象包含对另一个对象的引用,假设内部对象没有任何其他引用,则将外部对象的唯一引用设置为 null 将导致整个对象被垃圾回收,包括其孤立的引用。
  • 在您提到的第 6 条中:清空对象引用应该是例外而不是常态。
【解决方案3】:

实例字段、数组元素

如果有一个对象的引用,它就不能被垃圾回收。尤其是如果那个对象(以及它后面的整个图)很大,那么只有一个引用正在停止垃圾收集,并且不再需要那个引用,这是一种不幸的情况。

病态的情况是对象保留了用于配置它的整个 XML DOM 树的不必要实例、未取消注册的 MBean,或者对阻止整个类加载器的未部署 Web 应用程序的对象的单一引用从被卸载。

因此,除非您确定持有引用本身的对象无论如何都会被垃圾回收(甚至在那时),否则您应该清除不再需要的所有内容。

作用域变量:

如果您正在考虑在其作用域结束之前将局部变量设置为 null ,以便它可以被垃圾收集器回收并将其标记为“从现在开始不可用”,您应该考虑将它放在一个更范围有限。

{
  BigObject obj = ...
  doSomethingWith(obj);
  obj = null;          //   <-- explicitly set to null
  doSomethingElse();
}

变成

{
  {  
     BigObject obj = ...
     doSomethingWith(obj);
  }    //         <-- obj goes out of scope
  doSomethingElse();
}

长而扁平的作用域通常也不利于代码的易读性。仅仅为了这个目的而引入私有方法来分解事物也并非闻所未闻。

【讨论】:

  • 如果您指的是强引用,是的,这是正确的,但并非适用于所有引用。 java中的弱引用可以被垃圾回收。
【解决方案4】:

在内存受限的环境(例如手机)中,这可能很有用。通过设置 null,objetc 不需要等待变量超出范围即可被 gc'd。

然而,对于日常编程,这不应该成为规则,除非在特殊情况下,例如 Chris Jester-Young 引用的那个。

【讨论】:

    【解决方案5】:

    首先,这并不意味着您将对象设置为null。我在下面解释:

    List list1 = new ArrayList();
    List list2 = list1;
    

    在上面的代码段中,我们正在创建存储在内存中的ArrayList 对象的对象引用变量名称list1。所以list1 指的是那个对象,它只不过是一个变量。在第二行代码中,我们将list1 的引用复制到list2。所以现在回到你的问题,如果我这样做:

    list1 = null;
    

    这意味着list1 不再引用存储在内存中的任何对象,因此list2 也将没有可引用的内容。所以如果你检查list2的大小:

    list2.size(); //it gives you 0
    

    所以这里出现了垃圾收集器的概念,它说 «你不必担心释放对象持有的内存,当我发现它不再在程序中使用时我会这样做,而 JVM 将管理我。»

    我希望它澄清了这个概念。

    【讨论】:

      【解决方案6】:

      这样做的原因之一是消除过时的对象引用。 可以看文here.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-09-03
        • 1970-01-01
        • 1970-01-01
        • 2016-03-07
        • 2014-10-27
        • 2010-10-09
        相关资源
        最近更新 更多