我最近正在研究释放Java对象占用的内存。在这样做的过程中,我对如何在Java中复制对象(浅/深)以及如何避免在对象仍在使用时意外清除/无效对象感到困惑。
考虑以下场景:
- 将CCD_ 1作为参数传递给方法
- 将CCD_ 2传递给要由线程处理的可运行类
- 将CCD_ 3放入CCD_
在这种情况下,如果我调用list = null;
或list.clear();
,对象会发生什么?在哪种情况下,对象会丢失,在哪些情况下,只有引用被设置为null?
我想这与对象的浅层和深层复制有关,但在哪些情况下会发生浅层复制,在Java中会发生深层复制?
首先,您从不将对象设置为null。这个概念毫无意义。您可以将null
的值分配给变量,但需要非常仔细地区分"变量"one_answers"对象"的概念。一旦你这样做了,你的问题就会自己回答:)
现在,就"浅层复制"与"深度复制"而言,这里可能值得避免使用"浅层副本"一词,因为通常浅层副本涉及创建新对象,但只是直接复制现有对象的字段。深度复制也将获取这些字段引用的对象的副本(对于引用类型字段)。一个简单的任务是:
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<Object>
0对象仍然不能被垃圾回收,因为Map
有一个引用它的条目。
清除变量
据我所知,
如果要重用变量,请使用
Object.clear();
如果你不打算重复使用,那么定义
Object=null;
注意:与removeAll()相比,clear()更快
请纠正我,如果我错了。。。。
这取决于有多少变量引用到每个对象,为了解释这一点,最好使用以下代码:
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作为参数传递给方法或类似方法,而是取决于有多少变量仍然引用您的对象。
如果将ArrayList传递给方法,那么如果在某个位置(例如调用代码中)存在对该列表的实时引用,则list=null将无效。如果在代码中的任何位置调用list.clear(),则对该列表中对象的引用将为null。传递对方法的引用不是浅层复制,而是通过值传递引用
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
}
}
如果将列表放入哈希映射中,则哈希映射现在包含对列表的引用。
如果将列表作为参数传递给某个方法,则在该方法的持续时间内,该方法将具有对它的引用。
如果将它传递给要操作的线程,则该线程将具有对该对象的引用,直到它终止。
在所有这些情况下,如果设置list = null
,则仍将保留引用,但在这些引用消失后,这些引用将消失。
如果你只是简单地清除列表,引用仍然有效,但现在会指向一个突然被清空的列表,这可能是程序员不知道的,可能被认为是一个错误,尤其是如果你使用线程。
我最近正在研究释放java对象占用的内存。
一条建议。
想想这个通常是个坏主意。而试图"帮助"通常是一个更糟糕的想法。在99.8%的情况下,Java垃圾收集器能够更好地收集垃圾,如果你真的让它继续下去的话。。。不要把null
分配给事物,浪费你的精力。事实上,很有可能您正在取消的字段位于无论如何都将变得不可访问的对象中。在这种情况下,GC甚至不会查看您为null的字段。
如果你采取这种(务实的)观点,那么你对浅拷贝与深拷贝的所有思考,以及何时可以安全地清空东西,都是没有意义的。
在极少数情况下,建议分配null
。。。以避免中长期储存泄漏。如果你处于一种罕见的情况,"回收"物体实际上是个好主意,那么作废也是可取的。