我最近正在考虑释放Java对象占用的内存。在这样做时,我对如何在Java中复制(浅/深)对象以及如何避免在对象仍处于使用状态时意外清除/无效而感到困惑。
请考虑以下情况:
ArrayList<Object>
作为参数传递给方法。ArrayList<Object>
传递给可由线程处理的可运行类。ArrayList<Object>
放入HashMap
。现在在这种情况下,如果我调用list = null;
或list.clear();
,对象会发生什么?在这种情况下,对象会丢失,在这种情况下,只将引用设置为null?
我想这与浅层和深层复制对象有关,但是在哪些情况下会发生浅层复制,在这种情况下是否会在Java中发生深层复制?
答案 0 :(得分:41)
首先,您从未将对象设置为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
有一个引用它的条目。
答案 1 :(得分:6)
清除变量
据我所知,
如果要重复使用该变量,请使用
Object.clear();
如果您不打算重复使用,请定义
Object=null;
注意: 与removeAll()相比,clear()更快。
请纠正我,如果我错了......
答案 2 :(得分: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作为参数传递给方法等,它取决于仍然有多少变量引用您的对象。
答案 3 :(得分: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
}
}
答案 4 :(得分:2)
如果您将一个ArrayList传递给一个方法,那么如果在某个地方(例如在调用代码中)存在对该列表的实时引用,则list = null将不起作用。如果在代码中的任何位置调用list.clear(),则对此列表中对象的引用将为空。传递对方法的引用不是浅层复制,而是传递引用按值
答案 5 :(得分:1)
如果将列表放入哈希映射中,则哈希映射现在包含对列表的引用。
如果将列表作为参数传递给方法,则该方法将在方法持续时间内对其进行引用。
如果将其传递给要操作的线程,则该线程将引用该对象,直到它终止。
在所有这些情况下,如果设置list = null
,引用仍将保留,但在这些引用消失后它们将消失。
如果您只是清除列表,引用仍然有效,但现在将指向突然清空的列表,程序员可能不知道这可能被视为错误,特别是如果您使用线程。
答案 6 :(得分:0)
我最近正在考虑释放java对象占用的内存。
一条建议。
考虑这一点通常是一个坏主意。尝试“帮助”通常是一个更糟糕的主意。在99.8%的情况下,Java垃圾收集器能够更好地收集垃圾,如果你实际上只是让它继续使用它......并且不要浪费你的精力来为事情分配null
。实际上,您正在归零的字段很可能是无论如何都要无法访问的对象。在这种情况下,GC甚至不会查看您已经填充的字段。
如果您采用这种(实用的)观点,那么您对浅层复制品和深层复制品的所有想法以及何时可以安全地废弃物品都是没有意义的。
有一小部分案例建议指定null
...以避免中期或长期存储泄漏。如果您处于“回收”对象的罕见情况之一实际上是一个好主意,那么建议使用归零。