86

在 Java中分配一个未使用的对象引用是否null以任何可衡量的方式改进了垃圾收集过程?

我在 Java(和 C#)方面的经验告诉我,尝试超越虚拟机或 JIT 编译器通常是违反直觉的,但我看到同事使用这种方法,我很好奇这是否是一个好的选择向上或那些巫毒编程迷信之一?

4

14 回答 14

66

通常,没有。

但就像所有事情一样:这取决于。现在 Java 中的 GC 非常好,所有东西都应该在它不再可用后很快被清理掉。这只是在为局部变量留下方法之后,并且不再为字段引用类实例时。

如果您知道它会以其他方式被引用,您只需要显式地为 null。例如一个被保留的数组。当不再需要数组的各个元素时,您可能希望它们为空。

例如,来自 ArrayList 的这段代码:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
         System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}

此外,只要没有引用保留,显式地清空一个对象不会比它自然超出范围更早被收集。

两个都:

void foo() {
   Object o = new Object();
   /// do stuff with o
}

和:

void foo() {
   Object o = new Object();
   /// do stuff with o
   o = null;
}

在功能上是等效的。

于 2009-01-16T03:38:10.440 回答
12

根据我的经验,人们常常出于偏执而不是出于必要而取消引用。这是一个快速指南:

  1. 如果对象 A 引用对象 B并且您不再需要此引用并且对象 A 不符合垃圾回收条件,那么您应该显式地清空该字段。如果封闭对象正在被垃圾收集,则无需将字段清空。在 dispose() 方法中清除字段几乎总是无用的。

  2. 无需将方法中创建的对象引用清空。一旦方法终止,它们将自动清除。此规则的例外情况是,如果您在一个非常长的方法或一些大型循环中运行,并且您需要确保在方法结束之前清除某些引用。同样,这些情况极为罕见。

我会说绝大多数时候你不需要取消引用。试图智取垃圾收集器是没有用的。你最终会得到低效、不可读的代码。

于 2009-01-16T03:52:51.737 回答
11

好文章是今天的编码恐怖

GC 的工作方式是寻找没有任何指向它们的指针的对象,它们的搜索区域是堆/堆栈以及它们拥有的任何其他空间。因此,如果您将变量设置为 null,则实际对象现在不会被任何人指向,因此可能会被 GC 处理。

但是由于 GC 可能不会在那个确切的时刻运行,你可能实际上并没有给自己买任何东西。但是,如果您的方法相当长(就执行时间而言),那么它可能是值得的,因为您将增加 GC 收集该对象的机会。

代码优化也可能使问题变得复杂,如果您在将变量设置为 null 后从不使用该变量,那么删除将值设置为 null 的行(少一条执行指令)将是一种安全的优化。所以你实际上可能没有得到任何改善。

总而言之,是的,它可以提供帮助,但它不是确定性的。

于 2009-01-16T03:42:07.407 回答
8

至少在 java 中,它根本不是伏都教编程。当您使用类似的东西在java中创建对象时

Foo bar = new Foo();

你做了两件事:第一,你创建一个对象的引用,第二,你创建 Foo 对象本身。只要存在该引用或其他引用,就不能 gc'd 特定对象。但是,当您将 null 分配给该引用时...

bar = null ;

并假设没有其他对象引用该对象,它会在下次垃圾收集器经过时被释放并可供 gc 使用。

于 2009-01-16T03:36:49.087 回答
7

这取决于。

一般而言,您保留对对象的引用越短,收集它们的速度就越快。

如果您的方法需要 2 秒才能执行,并且在方法执行一秒后您不再需要对象,则清除对它的任何引用是有意义的。如果 GC 在一秒钟后发现你的对象仍然被引用,那么下次它可能会在一分钟左右检查它。

无论如何,默认情况下将所有引用设置为 null 对我来说是过早的优化,除非在特定的极少数情况下可以显着减少内存消耗,否则没有人应该这样做。

于 2009-01-16T03:50:14.910 回答
7

显式设置对 null 的引用而不是让变量超出范围,这对垃圾收集器没有帮助,除非持有的对象非常大,在完成后立即将其设置为 null 是个好主意。

通常将引用设置为null,意味着该对象已完全完成的代码的READER,不应再担心。

可以通过添加一组额外的大括号来引入更窄的范围来实现类似的效果

{
  int l;
  {  // <- here
    String bigThing = ....;
    l = bigThing.length();
  }  // <- and here
}

这允许 bigThing 在离开嵌套大括号后立即被垃圾收集。

于 2009-01-16T08:08:16.387 回答
6
public class JavaMemory {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            byte[] data = new byte[dataSize];
            //data = null;
        }

        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {

        JavaMemory jmp = new JavaMemory();
        jmp.f();

    }

}

上面的程序抛出OutOfMemoryError. 如果您取消注释data = null;OutOfMemoryError则解决。将未使用的变量设置为 null 始终是一个好习惯

于 2009-06-02T05:58:50.690 回答
4

有一次我正在开发一个视频会议应用程序,当我不再需要该对象时花时间将引用归零时,我注意到性能上的巨大差异。那是在 2003-2004 年,我只能想象 GC 从那以后变得更加智能。在我的例子中,我每秒有数百个对象进出范围,所以当它定期启动时我注意到了 GC。但是,在我指出空对象后,GC 停止暂停我的应用程序。

所以这取决于你在做什么......

于 2012-12-09T17:33:28.813 回答
2

是的。

来自“实用程序员”第 292 页:

通过设置对 NULL 的引用,您可以将指向对象的指针数量减少一......(这将允许垃圾收集器将其删除)

于 2009-01-16T03:35:51.707 回答
1

我假设OP指的是这样的事情:

private void Blah()
{
    MyObj a;
    MyObj b;

    try {
        a = new MyObj();
        b = new MyObj;

        // do real work
    } finally {
        a = null;
        b = null;
    }
}

在这种情况下,VM 不会在它们离开范围后立即将它们标记为 GC 吗?

或者,从另一个角度来看,如果项目超出范围,是否会显式地将项目设置为 null 导致它们先被 GC 处理?如果是这样,VM 可能会在不需要内存时花时间 GC'ing 对象,这实际上会导致更糟糕的性能 CPU 使用,因为它会更早地 GC'ing。

于 2009-01-16T03:44:18.853 回答
1

即使取消引用效率稍微高一点,是否值得在代码中添加这些丑陋的无效值?它们只会使包含它们的意图代码变得混乱和模糊。

它是一个罕见的代码库,没有比试图超越垃圾收集器更好的优化候选者(更罕见的是成功超越它的开发人员)。您的努力很可能会更好地花在其他地方,放弃那个笨拙的 Xml 解析器或寻找一些缓存计算的机会。这些优化将更容易量化,并且不需要你用噪音弄脏你的代码库。

于 2009-11-18T18:33:09.237 回答
0

这取决于

我不了解 Java,但在 .net(C#、VB.net...)中,当您不再需要对象时,通常不需要分配 null。

但是请注意,它“通常不是必需的”。

通过分析您的代码,.net 编译器可以很好地评估变量的生命周期......以准确判断何时不再使用该对象。因此,如果您编写 obj=null 它实际上可能看起来好像仍在使用 obj...在这种情况下,分配 null 会适得其反。

在某些情况下,它实际上可能有助于分配空值。一个例子是你有一个运行很长时间的巨大代码,或者一个在不同线程或某个循环中运行的方法。在这种情况下,分配 null 可能会有所帮助,以便 GC 很容易知道它不再被使用。

对此没有硬性规定。在您的代码中通过上述位置进行空分配并运行分析器以查看它是否有任何帮助。很可能您可能看不到任何好处。

如果您尝试优化的是 .net 代码,那么我的经验是,小心处理 Dispose 和 Finalize 方法实际上比担心 null 更有益。

关于该主题的一些参考资料:

http://blogs.msdn.com/csharpfaq/archive/2004/03/26/97229.aspx

http://weblogs.asp.net/pwilson/archive/2004/02/20/77422.aspx

于 2009-01-16T03:42:18.997 回答
0

在您的程序的未来执行中,一些数据成员的值将用于计算程序外部可见的输出。其他的可能会或可能不会被使用,这取决于程序的未来(并且不可能预测)输入。可能保证不使用其他数据成员。分配给那些未使用数据的所有资源,包括内存,都被浪费了。垃圾收集器 (GC) 的工作是消除浪费的内存。GC 消除需要的东西将是灾难性的,因此使用的算法可能是保守的,保留超过严格的最小值。它可能会使用启发式优化来提高其速度,但代价是保留一些实际不需要的项目。GC 可能使用许多潜在的算法。因此,您对程序所做的更改是可能的,并且不会影响程序的正确性,但可能会影响 GC 的操作,或者使其运行更快地完成相同的工作,或者更快地识别未使用的项目。所以这种改变,设置一个unusdd对象引用null理论上并不总是巫毒。

是巫毒吗?据报道,有部分 Java 库代码可以做到这一点。该代码的编写者比普通程序员要好得多,并且要么了解或与了解垃圾收集器实现细节的程序员合作。所以这表明有时会有好处。

于 2017-11-09T07:40:42.417 回答
0

正如您所说,有一些优化,即 JVM 知道上次使用变量的位置,并且可以在最后一点之后立即对它引用的对象进行 GC(仍在当前范围内执行)。因此,在大多数情况下,将引用归零对 GC 没有帮助。

但避免“裙带关系”(或“浮动垃圾”)问题可能很有用(在此处阅读更多信息观看视频)。问题的存在是因为堆被分为老年代和年轻代,并且应用了不同的 GC 机制:Minor GC(速度很快,并且经常发生在清理年轻代)和 Major Gc(导致清理老代的更长时间的暂停)。“裙带关系”不允许收集年轻代中的垃圾,如果它被已经被老代使用的垃圾所引用。

这是“病态的”,因为任何提升的节点都会导致所有后续节点的提升,直到 GC 解决问题。

为了避免裙带关系,最好从应该删除的对象中清除引用。您可以在 JDK 类中看到这种技术:LinkedListLinkedHashMap

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    // ...
}
于 2020-06-22T23:08:33.910 回答