24

我最近正在研究释放 Java 对象占用的内存。这样做时,我对如何在 Java 中复制(浅/深)对象以及如何避免在对象仍在使用时意外清除/无效对象感到困惑。

考虑以下场景:

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

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

我想这与对象的浅拷贝和深拷贝有关,但是在哪些情况下会发生浅拷贝,在哪些情况下会在 Java 中发生深拷贝?

4

7 回答 7

54

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

现在就“浅拷贝”与“深拷贝”而言——这里可能值得避免使用“浅拷贝”一词,因为通常浅拷贝涉及创建一个新对象,而只是直接复制现有对象的字段。深层副本也将获取这些字段引用的对象的副本(对于引用类型字段)。像这样的简单任务:

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

... 从这个意义上说,既不做浅拷贝,也不做深拷贝。它只是复制参考。在上面的代码之后,list1并且list2是自变量——它们现在恰好具有相同的值(引用)。我们可以改变其中一个的值,而不会影响另一个:

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

现在,如果我们不更改变量,而是更改变量值所引用的对象,那么该更改也将通过另一个变量可见:

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

所以回到你原来的问题——你永远不会将实际对象存储在地图、列表、数组等中。你只会存储引用。只有当“实时”代码无法再到达该对象时,才能对对象进行垃圾回收。所以在这种情况下:

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

...该ArrayList对象仍然不能被垃圾收集,因为它Map有一个引用它的条目。

于 2013-08-02T06:19:35.047 回答
9

清除变量

据我所知,

如果要重用变量,请使用

               Object.clear();

如果你不打算重用,那么定义

               Object=null;

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

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

于 2014-05-20T10:04:08.563 回答
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
    }
}
于 2013-08-02T06:23:02.263 回答
2

如果您将 ArrayList 传递给一个方法,那么如果在某处(例如在调用代码中)存在对列表的实时引用,则 list = null 将无效。如果您在代码中的任何位置调用 list.clear() ,则对该列表中对象的引用将被清空。传递对方法的引用不是浅拷贝,而是按值传递引用

于 2013-08-02T06:24:42.647 回答
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 作为参数传递给方法等,它取决于仍有多少变量引用您的对象。

于 2013-08-02T06:18:50.720 回答
1

如果将列表放入哈希映射中,哈希映射现在包含对列表的引用。

如果您将列表作为参数传递给方法,则该方法将在方法期间引用它。

如果将它传递给要操作的线程,则线程将拥有对该对象的引用,直到它终止。

在所有这些情况下,如果您设置list = null,引用仍将保持,但在这些引用消失后它们将消失。

如果您只是清除列表,引用仍然有效,但现在将指向一个突然被清空的列表,这可能是程序员不知道的并且可能被认为是一个错误,特别是如果您使用线程。

于 2013-08-02T06:21:47.497 回答
0

我最近正在研究释放 java 对象占用的内存。

一个忠告。

考虑这一点通常是个坏主意。尝试“帮助”通常是一个更糟糕的主意。在 99.8% 的情况下,如果你真的让它继续处理,Java 垃圾收集器能够更好地收集垃圾......并且不要浪费你的精力来分配null东西。实际上,您正在清空的字段很有可能位于无论如何都将变得无法访问的对象中。在这种情况下,GC 甚至不会查看您已清空的字段。

如果您采用这种(务实的)观点,那么您对浅拷贝与深拷贝以及何时可以安全地清空事物的所有想法都是没有意义的。


在极少数情况下,建议分配null... 以避免中长期存储泄漏。如果您处于“回收”对象实际上是一个好主意的罕见情况之一,那么清空也是可取的。

于 2013-08-02T06:58:25.180 回答