将Java对象设置为null是否会做任何事情?


112

我在浏览一些旧书时,发现了彼得·哈格(Peter Hagger)的“ Practical Java”的副本。在性能部分,建议null不再使用对象引用。

在Java中,设置对象引用是否可以null提高性能或垃圾回收效率?如果是这样,在什么情况下会出现问题?容器类?对象组成?匿名内部类?

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


2
剖析它。在现代运行时中,您应该不会看到性能或内存占用量有任何有意义的增加。
杰森·可可

12
@Jason,个人资料吗?假设我将分析足够多的案例,以获得足够好的结果集来回答这一问题。而且我没有选择VM进行充分优化以掩盖gc和性能问题的情况。这就是为什么我在这里问这个。了解这是一个问题的情况。
萨尔

Answers:


73

这取决于您何时考虑使引用无效。

如果您有一个对象链A-> B-> C,则一旦无法访问A,A,B和C都将有资格进行垃圾回收(假设其他任何东西都没有提及B或C)。例如,没有必要,也从来没有需要将引用A-> B或B-> C显式设置为null。

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

其中存在的情况下使用的是一些建议以空集的引用被具体地在长范围,其中一个存储器密集型对象不再是通过使用范围的中途。例如:

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

这里的理由是,因为obj仍在范围内,所以没有对引用进行显式的null删除,直到doSomethingElse()方法完成后,它才变得可垃圾回收。这是现代JVM上可能不再适用的建议:事实证明,JIT编译器可以在不再使用给定本地对象引用的情况下进行计算。


2
局部变量可以由机器优化。类变量不能。我已经看到(线程池上的)工作线程在处理请求之后不会释放它们的状态对象,从而将这些对象固定在内存中,直到工作线程被赋予新任务(该任务立即用新任务覆盖了旧状态对象)。使用大型状态对象和数百个工作线程(例如:大型http服务器),这可能会保存并复制大量内存,但永远不会再使用。
布赖恩·怀特

1
我可能应该补充一下:我上面给出的建议是一般遵循的建议,假设行为良好的库,行为良好的VM等。如果您发现有一个线程池库,该线程池库在之后挂接到对象一项工作已经完成,好吧……这是上述线程池库中的一个错误,可以通过使对象引用为空来解决,但同样可以通过使用较少错误的线程池来解决。我不确定这种错误是否会改变一般的设计原则。
尼尔·科菲,2015年

25

不,这不是过时的建议。悬挂引用仍然是一个问题,尤其是如果要ArrayList使用预分配的数组实现可扩展数组容器(或类似对象)的话。超出列表“逻辑”大小的元素应被清空,否则它们将不会被释放。

请参阅有效的Java第二版,项目6:消除过时的对象引用。


这是语言问题还是VM实施问题?
巴勃罗·圣克鲁斯

4
这是一个“语义”问题。基本上,因为您已经预先分配了阵列,所以VM会看到。它对容器的“逻辑”大小一无所知。假设您有一个大小为10的ArrayList,由16个元素的数组支持。VM无法得知未实际使用10..15项;如果这些插槽中有内容,它们将不会被释放。
克里斯·杰斯特·杨

在容器类之外怎么办?在对象组合中,内部对象不被外部对象取消分配。
萨尔

7
@sal通常的经验法则是,如果您不能引用它,它将被垃圾收集。因此,如果外部对象包含对另一个对象的引用,并假定内部对象没有其他引用,则将外部对象的一个​​(仅引用)设置为null将导致整个对象(包括其孤立的引用)被垃圾回收。
ubermensch,2009年

2
在同一项目6中,您提到:排除对象引用应该是例外,而不是规范。
ACV

10

实例字段,数组元素

如果存在对对象的引用,则无法对其进行垃圾回收。尤其是如果该对象(及其后面的整个图形)很大,则只有一个引用正在停止垃圾收集,并且不再需要该引用了,这是一种不幸的情况。

病理情况是对象保留了用于配置它的整个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中的弱引用可以被垃圾收集。
James Drinkard

5

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

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


1

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

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

在上面的代码段中,我们正在创建存储在内存中list1ArrayList对象的对象引用变量名称。list1引用该对象也是如此,它不过是一个变量。而在第二行代码中,我们要复制的参考list1list2。所以现在回到您的问题,如果我这样做:

list1 = null;

这意味着list1不再引用存储在内存中的任何对象,因此list2也就没有要引用的对象。因此,如果您检查的大小list2

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

因此,这里出现了垃圾收集器的概念,它表示“您不必担心释放对象所持有的内存,当我发现它将不再用在程序中并且JVM将管理我时,我将这样做。”

我希望它能弄清楚这个概念。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.