在Java中清除或将对象设置为空

26

我最近在研究如何释放占用Java对象的内存。在此期间,我对Java中对象的复制(浅复制/深复制)以及如何避免在对象仍在使用时意外清除/将其设置为null感到困惑。

考虑以下场景:

  1. 将一个ArrayList<Object>作为参数传递给方法。
  2. ArrayList<Object>传递给可由线程处理的可运行类。
  3. ArrayList<Object>放入HashMap中。

那么,在这些情况下,如果我调用list = null;list.clear();,对象会发生什么?在哪种情况下对象会丢失,在哪种情况下仅引用被设置为null?

我想这与对象的浅复制和深复制有关,但在Java中,什么情况下会发生浅复制,什么情况下会发生深复制?


2
我一直觉得Java最吸引人的方面之一就是垃圾回收器... - Jannis Alexakis
7个回答

58

首先,你永远不应该将一个对象赋值为null。这个概念是没有意义的。你可以将null赋值给一个变量,但你需要非常仔细地区分“变量”和“对象”的概念。一旦你做到了,你的问题就会自己得到解答 :)

现在谈到“浅拷贝”和“深拷贝”——在这里最好避免使用“浅拷贝”这个术语,因为通常浅拷贝涉及创建一个新对象,但只是直接复制现有对象的字段。而深拷贝也会复制这些字段所引用的对象(对于引用类型字段)。像这样的简单赋值:

ArrayList<String> list1 = new ArrayList<String>();
ArrayList<String> list2 = list1;

在这种意义下,它既不进行浅拷贝也不进行深拷贝。它只是复制引用。在上面的代码之后,list1list2 是独立的变量——它们只是在此时具有相同的值(引用)。我们可以更改其中一个的值,而不会影响另一个:

list1 = null;
System.out.println(list2.size()); // Just prints 0

现在,如果我们不改变变量,而是对变量的值所指向的对象进行更改,那么这个更改将通过另一个变量也可见:

list2.add("Foo");
System.out.println(list1.get(0)); // Prints Foo

回到你最初的问题 - 在map、list、array等中,你从来没有存储过实际对象。你只存储引用。只有当没有任何“活”的代码能够访问该对象时,对象才能被垃圾回收。因此,在这种情况下:

List<String> list = new ArrayList<String>();
Map<String, List<String>> map = new HashMap<String, List<String>>();
map.put("Foo", list);
list = null;

...ArrayList对象仍然无法被垃圾回收,因为Map具有指向它的条目。


3
谢谢你澄清了这与浅复制或深复制无关的问题! - anzaan
实际上,你所说的有点不对...以下代码: ArrayList<String> list1 = new ArrayList<String>(); list1.add("yoyo"); list1.add("yoyo2"); System.out.println(list1.size()); ArrayList<String> list2 = list1; list1 = null; System.out.println(list2.size()); 将会输出 2 2。 - pinpinokio
1
@pinpinokio:这与我说的有什么矛盾之处?这正是我预期它要打印的内容,就我所知,它并没有违背答案中的任何内容。你认为答案会预测那个结果是什么,并且你认为答案的哪个部分与此相关? - Jon Skeet
啊,是的,我的错 - 我误读了你的答案 - 但仍然有点困惑,因为对于知识较少的人来说,看到“list1 = null;”可能会认为这使得“list2.size()”等于0 :) 但是,是的,抱歉,错误在我... - pinpinokio
@pinpinokio:将一个变量设置为null来“清除”列表会是一种非常奇怪的情况。我认为现在的答案已经很清晰了,我很满意。 - Jon Skeet
@JonSkeet 把一个变量设置为 null 以清除列表,这正是新手会做的事情,所以这并不奇怪。而且既然这是一个新手问题,我认为期望他们理解错是很正常的 - 但是,是的,这个答案足够清晰,所以再次强调 - 不需要更改任何东西。 - pinpinokio

9

清除变量

据我所知,

如果您要重复使用变量,则使用

               Object.clear();

如果您不打算重复使用,请定义。
               Object=null;

注意: 与 removeAll() 相比,clear() 更快。

如果我错了,请纠正我....


2

这取决于有多少变量引用了你的每个对象,为了解释这一点,最好提供一些代码:

Object myAwesomeObject = new Object();
List<Object> myList = new ArrayList<Object>();

myList.add(myAwesomeObject);

myList = null; // Your object hasn't been claimed by the GC just yet, your variable "myAwesomeObject" is still refering to it

myAwesomeObject = null; // done, now your object is eligible for garbage collection.

因此,它不取决于您是否将ArrayList作为参数传递给方法或类似方法,而是取决于仍然引用您的对象的变量数量。


你忘了:myList.add(myAwesomeObject); :))) - Nir Alfasi

2

Java GC会在对象不再被引用时自动回收它们。因此,在大多数情况下,您需要将引用显式地设置为null

一旦变量的作用域结束,如果没有其他引用指向该对象,则该对象将成为GC的候选对象并且被释放。

Java是按值传递的,因此如果您在方法中将列表设置为null,则不会影响在该方法中传递给您的原始引用。

public class A{

    private List<Integer> list = new ArrayList<Integer>();
    public static void main(String[] args) {
       A a = new A();
       B b = new B();

       b.method(a.list);

       System.out.println(a.list.size()); //Will print 0 and not throw NullPointerException
    }   

}

class B{
    public void method(List<Integer> list){
        list = null;
        //just this reference is set to null and not the original one
        //so list of A will not be GCed
    }
}

2
如果您将ArrayList传递给一个方法,则list = null不会产生影响,如果在调用代码中有列表的实时引用。如果在代码的任何地方调用list.clear(),则来自该列表的对象的引用将被清空。将引用传递给方法不是浅复制,而是按值传递引用。

1
如果您将列表放入哈希映射中,哈希映射现在持有对该列表的引用。
如果您将列表作为参数传递给方法,则该方法将在方法执行期间拥有对它的引用。
如果将其传递给线程进行操作,则该线程将拥有该对象的引用直到线程终止。
在所有这些情况下,如果设置list = null,则引用仍将被维护,但它们将在这些引用消失后消失。
如果您只是清空列表,则引用仍将有效,但现在将指向一个已经突然被清空的列表,这可能是程序员不知道的原因,并且可能被认为是错误,特别是如果您使用线程。

0
最近我在研究如何释放被Java对象占用的内存。
给你一个建议。
通常来说,考虑这个问题是一个不好的想法。试图“帮助”通常会更糟糕。在99.8%的情况下,如果你让Java垃圾回收器自己处理,它能够更好地收集垃圾,而不是浪费你的精力将东西赋值为null。实际上,你要置空的字段很可能在即将变得无法访问的对象中。在这种情况下,GC甚至不会查看你已经置空的字段。
如果你采取这种(实用主义)观点,你所有关于浅拷贝和深拷贝以及何时安全地置空东西的思考都是无意义的。

只有极少数情况下建议将null赋值给变量,以避免中长期的存储泄漏。如果你正处于那些罕见的情况之一,实际上 "回收" 对象是个好主意,那么将其置为空也是明智的。


请问“不可达”是什么意思?这是否意味着当一个方法结束时,所有本地变量都可以进行垃圾回收? - Diego Ramos
不可访问 是一个技术术语;请参见 https://dev59.com/EG035IYBdhLWcg3wBLXb 了解说明。 - Stephen C
1
你需要区分变量和对象。局部变量在堆栈上,而堆栈不受垃圾回收的影响。然而,变量可能包含对对象的引用,而对象在堆中,并且如果它们无法访问,则会受到垃圾回收的影响。 - Stephen C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接