【发布时间】:2010-10-01 18:06:34
【问题描述】:
在 Java 中将未使用的对象引用分配给 null 是否以任何可衡量的方式改进了垃圾收集过程?
我在 Java(和 C#)方面的经验告诉我,尝试超越虚拟机或 JIT 编译器通常是违反直觉的,但我看到同事使用这种方法,我很好奇这是否是一个好方法练习拿起或那些巫毒编程迷信之一?
【问题讨论】:
标签: java null garbage-collection
在 Java 中将未使用的对象引用分配给 null 是否以任何可衡量的方式改进了垃圾收集过程?
我在 Java(和 C#)方面的经验告诉我,尝试超越虚拟机或 JIT 编译器通常是违反直觉的,但我看到同事使用这种方法,我很好奇这是否是一个好方法练习拿起或那些巫毒编程迷信之一?
【问题讨论】:
标签: java null garbage-collection
通常不会。
但就像所有事情一样:这取决于。这些天 Java 中的 GC 非常好,所有东西都应该在它不再可用后很快被清理掉。这只是在为局部变量留下方法之后,并且不再为字段引用类实例时。
如果你知道它会被引用,你只需要显式地为空。例如一个被保留的数组。当不再需要数组的各个元素时,您可能希望它们为空。
例如,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;
}
在功能上是等价的。
【讨论】:
System.gc() 时,它将被删除(前提是没有其他指向该对象的引用)。
根据我的经验,人们常常出于偏执而不是出于必要而取消引用。这是一个快速指南:
如果对象 A 引用对象 B 并且您不再需要此引用 并且对象 A 不符合垃圾回收条件,那么您应该明确地清空该字段.如果封闭对象正在被垃圾收集,则无需将字段清空。在 dispose() 方法中清空字段几乎总是没用的。
无需将方法中创建的对象引用清空。一旦方法终止,它们将自动清除。此规则的例外情况是,如果您正在运行一个非常长的方法或一些大型循环,并且您需要确保在方法结束之前清除某些引用。同样,这些情况极为罕见。
我会说绝大多数情况下您都不需要将引用归零。试图智取垃圾收集器是没有用的。你最终会得到低效、不可读的代码。
【讨论】:
好文章是今天的coding horror。
GC 的工作方式是寻找没有指向它们的指针的对象,它们的搜索区域是堆/堆栈以及它们拥有的任何其他空间。因此,如果您将变量设置为 null,则实际对象现在不会被任何人指向,因此可能会被 GC。
但由于 GC 可能不会在那个确切的时刻运行,您可能实际上并没有给自己买任何东西。但是,如果您的方法相当长(就执行时间而言),那么它可能是值得的,因为您将增加 GC 收集该对象的机会。
问题也可能因代码优化而变得复杂,如果您在将变量设置为 null 后从不使用该变量,那么删除将值设置为 null 的行(少一条执行指令)将是一种安全的优化。所以你可能实际上并没有得到任何改善。
总而言之,是的,它可以提供帮助,但它不是确定性的。
【讨论】:
至少在 java 中,它根本不是伏都教编程。当您使用类似的东西在java中创建对象时
Foo bar = new Foo();
你做了两件事:第一,你创建一个对象的引用,第二,你创建 Foo 对象本身。只要存在该引用或其他引用,就不能 gc'd 特定对象。但是,当您将 null 分配给该引用时...
bar = null ;
并假设没有其他对象引用该对象,下次垃圾收集器经过时,它会被释放并可供 gc 使用。
【讨论】:
视情况而定。
一般而言,您保留对对象的引用的时间越短,收集它们的速度就越快。
如果您的方法需要 2 秒才能执行,并且在方法执行一秒后您不再需要对象,则清除对它的任何引用是有意义的。如果 GC 在一秒钟后发现,你的对象仍然被引用,下次它可能会在一分钟左右检查它。
无论如何,默认情况下将所有引用设置为 null 对我来说是过早的优化,除非在特定的极少数情况下显着减少内存消耗,否则没人应该这样做。
【讨论】:
显式设置对 null 的引用而不是仅仅让变量超出范围,这对垃圾收集器没有帮助,除非持有的对象非常大,在完成后立即将其设置为 null 是一个不错的选择想法。
一般将引用设置为null,意味着该对象已完全完成的代码的READER,不应再关心。
通过添加一组额外的大括号来引入更窄的范围,可以实现类似的效果
{
int l;
{ // <- here
String bigThing = ....;
l = bigThing.length();
} // <- and here
}
这允许 bigThing 在离开嵌套大括号后立即被垃圾回收。
【讨论】:
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 始终是一种好习惯
【讨论】:
有一次我正在开发一个视频会议应用程序,当我不再需要对象时花时间将引用归零时,我注意到性能上的巨大差异。那是在 2003-2004 年,我只能想象 GC 从那以后变得更加智能。在我的例子中,我每秒有数百个对象进出范围,所以当它定期启动时我注意到了 GC。但是,在我指出空对象后,GC 停止暂停我的应用程序。
所以这取决于你在做什么......
【讨论】:
是的。
来自《实用程序员》第 292 页:
通过设置对 NULL 的引用,您可以将指向对象的指针数量减少一...(这将允许垃圾收集器将其删除)
【讨论】:
我认为 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。
【讨论】:
null 会影响 GC 行为。
即使取消引用的效率稍微高一些,是否值得在代码中添加这些丑陋的无效值?它们只会使包含它们的意图代码变得混乱和模糊。
这是一个罕见的代码库,没有比试图智取垃圾收集器更好的优化候选者(成功智取垃圾收集器的开发人员仍然很少)。你的努力很可能会更好地花在其他地方,放弃那个笨拙的 Xml 解析器或寻找一些缓存计算的机会。这些优化将更容易量化,并且不需要你用噪音弄脏你的代码库。
【讨论】:
"视情况而定"
我不了解 Java,但在 .net(C#、VB.net...)中,当您不再需要对象时,通常不需要分配 null。
但请注意,“通常不需要”。
通过分析您的代码,.net 编译器可以很好地评估变量的生命周期...以准确判断何时不再使用该对象。因此,如果您编写 obj=null,它实际上可能看起来好像仍在使用 obj……在这种情况下,分配 null 会适得其反。
在某些情况下,分配空值实际上可能会有所帮助。一个例子是你有一个运行很长时间的巨大代码,或者一个在不同线程或某个循环中运行的方法。在这种情况下,分配 null 可能会有所帮助,以便 GC 很容易知道它不再被使用。
对此没有硬性规定。在您的代码中通过上述位置进行空分配并运行分析器以查看它是否有任何帮助。您很可能看不到任何好处。
如果您尝试优化的是 .net 代码,那么我的经验是,妥善处理 Dispose 和 Finalize 方法实际上比为空值烦恼更有益。
关于该主题的一些参考资料:
http://blogs.msdn.com/csharpfaq/archive/2004/03/26/97229.aspx
http://weblogs.asp.net/pwilson/archive/2004/02/20/77422.aspx
【讨论】:
在您的程序的未来执行中,一些数据成员的值将用于计算程序外部可见的输出。其他的可能会或可能不会被使用,这取决于程序的未来(并且不可能预测)输入。可能保证不使用其他数据成员。分配给那些未使用数据的所有资源,包括内存,都被浪费了。垃圾收集器 (GC) 的工作是消除浪费的内存。 GC 消除需要的东西将是灾难性的,因此使用的算法可能是保守的,保留超过严格的最小值。它可能会使用启发式优化来提高其速度,但代价是保留一些实际不需要的项目。 GC 可能使用许多潜在的算法。因此,您对程序所做的更改可能不会影响程序的正确性,但仍可能会影响 GC 的操作,或者使其运行更快以完成相同的工作,或者更快地识别未使用的项目。所以这种改变,设置一个 unusdd 对象引用到null,理论上并不总是巫术。
是巫毒吗?据报道,有部分 Java 库代码可以做到这一点。该代码的编写者比普通程序员要好得多,并且要么了解或与了解垃圾收集器实现细节的程序员合作。这表明 有时会有好处。
【讨论】:
正如您所说,有一些优化,即 JVM 知道上次使用变量的位置,并且可以在最后一点之后立即对它引用的对象进行 GC(仍在当前范围内执行)。所以在大多数情况下,清空引用对 GC 没有帮助。
但避免“裙带关系”(或“浮动垃圾”)问题(read more here 或 watch video)可能很有用。问题的存在是因为堆被分为老年代和年轻代,并且应用了不同的 GC 机制:Minor GC(速度很快,并且经常发生在清理年轻代)和 Major Gc(导致清理老代的更长时间的暂停)。 “裙带关系”不允许在 Young gen 中的垃圾被已经被老一代所使用的垃圾所引用。
这是“病态的”,因为任何提升的节点都会导致所有后续节点的提升,直到 GC 解决问题。
为了避免裙带关系,最好从应该删除的对象中清除引用。您可以在 JDK 类中看到这种技术:LinkedList 和 LinkedHashMap
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
// ...
}
【讨论】: