ChatGPT解决这个技术问题 Extra ChatGPT

在 Java 中将对象分配给 null 会影响垃圾收集吗?

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

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


M
Mark Renouf

通常,没有。

但就像所有事情一样:这取决于。现在 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;
}

在功能上是等效的。


您可能还希望在可能使用大量内存或需要很长时间执行的方法中或在可能运行很长时间的 Swing 应用程序中的代码中对引用进行空引用。在简短的方法或短暂的对象中,不值得额外混乱的代码。
那么,当我们为一个对象清空时,垃圾收集器会立即收集该对象是否正确?
不会。垃圾收集器会定期执行以查看是否有没有引用变量指向的对象。当您将它们设置为 null 然后调用 System.gc() 时,它将被删除(前提是没有其他指向该对象的引用)。
@JavaTechnical 据我所知,不能保证通过调用 System.gc() 垃圾收集开始。
2
2 revs, 2 users 92%

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

如果对象 A 引用对象 B 并且您不再需要此引用并且对象 A 不符合垃圾回收条件,那么您应该显式地清空该字段。如果封闭对象正在被垃圾收集,则无需将字段清空。在 dispose() 方法中清除字段几乎总是无用的。无需将方法中创建的对象引用清空。一旦方法终止,它们将自动清除。此规则的例外情况是,如果您正在运行一个非常长的方法或一些大型循环,并且您需要确保在方法结束之前清除某些引用。同样,这些情况极为罕见。

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


e
earlNameless

好文章是今天的coding horror

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

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

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

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


安全优化的要点是删除将 ref 设置为 null 的行。
优化器可能会删除 null 分配,但与此同时,可以理解该变量未使用,因此不计为防止垃圾收集的引用。
C
Charlie Martin

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

Foo bar = new Foo();

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

bar = null ;

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


但是如果没有额外的代码行,超出范围也会做同样的事情。如果它是方法的本地,则没有必要。离开该方法后,该对象将符合 GC 条件。
好的。现在,对于一个流行测验,构建一个还不够的代码示例。提示:它们存在。
l
lubos hasko

这取决于。

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

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

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


T
Thorbjørn Ravn Andersen

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

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

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

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

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


不错的选择。这避免了将变量显式设置为 null 以及一些 IDE 显示有关未使用该 null 分配的 lint 警告。
o
omiel
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 始终是一个好习惯


也就是说,imo,是对稻草人争论世界的一次坚实的尝试。仅仅因为您可以创建一种情况,将变量设置为 null 将解决该特定情况下的问题(我不相信您已经这样做了)并不意味着在每种情况下将其设置为 null 都是一个好主意。添加代码以将不再使用的每个变量设置为 null,即使它们此后不久就会超出范围,这只会增加代码膨胀并降低代码的可维护性。
为什么在变量数据超出范围时不收集 byte[] 数组垃圾?
@Grav:超出范围并不能保证垃圾收集器会收集本地对象。但是,将其设置为 null 可以防止 OutOfMemory 异常,因为您可以保证 GC 在抛出 OutOfMemory 异常之前运行,并且如果对象设置为 null,它将是可回收的。
P
Perry

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

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


佚名

是的。

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

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


我想这仅适用于引用计数算法。是在使用还是在其他方面?
对于任何现代垃圾收集器来说,这个建议已经过时了。而引用计数GC有很大的问题,比如循环引用。
在标记和清除 GC 中也是如此;可能在所有其他人中。少一个引用这一事实并不意味着它是引用计数的。 tunet 的回答解释了这一点。缩小范围比设置为 NULL 更好。
@LawrenceDol 答案并不过时。另请注意,James Brooks 没有谈论引用计数 GC 算法。如果对象被活动对象引用,任何 GC 实现都不会对对象进行垃圾收集。因此,作为一般规则,通过将引用设置为 null 来删除引用会极大地影响 GC,因为它降低了活动引用指向对象的可能性。当然,它是否影响特定的代码是一个完全不同的主题,需要进行完全具体的讨论。
C
CodingWithSpike

我假设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。


GC 运行的时间是不确定的。我不相信将对象设置为 null 会影响 GC 行为。
如果对 null 的赋值导致计数变为零,则引用计数系统将需要立即采取行动。但在这种情况下,离开该方法会产生相同的效果,因为它也会取消引用对象。遍历垃圾收集器并不关心这些分配,因为只要执行环境认为是运行的好时机,它们就会运行。
s
sgargan

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

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


S
Sesh

“这取决于”

我不了解 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


R
Raedwald

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

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


K
Kirill

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

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

这是“病态的”,因为任何提升的节点都会导致所有后续节点的提升,直到 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
    // ...
}