为什么要手动消除过期对象的引用
虽然JDK自带垃圾回收机制会自动回收不再使用的对象,但是JDK的垃圾回收机制并没有那么智能,仍会存在“内存泄漏”问题,例如下面的例子:
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 8;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
//elements[size] = null;
return result;
}
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size +1);
}
}
@Override
public String toString() {
return "Stack [elements=" + Arrays.toString(elements) + ", size=" + size + "]";
}
public static void main(String[] args) {
Stack s = new Stack();
s.push("hua");
s.push(new Object());
s.push(90);
System.out.println(s);
System.out.println(s.pop());
System.out.println(s);
}
}
测试结果如下:
从测试结果中我们可以看到虽然我们已经弹出了90,但是在栈中并没有删除,仍然存在,在之后的一些操作仍然可以被使用,这样就会造成误操作。
上面的例子我们看到了存在的问题,程序中发生了内存泄漏,可以看出一个栈先增长再收缩,那么从栈中弹出来的对象就不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,他们也不会被回收。这是因为栈内部维护者对这些过期对象的引用。
在支持垃圾回收的语言中,内存泄漏时很隐蔽的(称这类内存泄漏为“无意识的对象保持”)。如果一个对象引用被无意识的保留了起来,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象。
这类问题修复的方法很简单:一旦对象引用已经过期,只需要清空这些引用即可。
修改方法,只需要修改pop()中的代码:
public Object pop() {
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
测试结果如下:
清空过期引用的另一个好处是,如果它们以后又被错误地解除引用,程序就会立即抛出NullPointerException异常,而不是悄悄地错误运行下去。
尽快地检测出程序中的错误总是有益的。消除过期引用最好的方法是让包含该引用的变量结束其生命周期。
那么什么时候应该清空引?
一个简单的规则:一旦数组元素变成了非活动部分的一部分,程序员就手工清空这些数组元素。
一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。
内存泄漏的另一个常见来源是缓存。一旦你把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。
内存泄漏的第三个常见来源是监听器和其他回调。
造成内存泄漏的原因是什么?
很多时候内存泄漏都是“人们无意识的对象引用”造成的。
List<String> list = new ArrayList<>();
String s = "testString";
list.add(s);
s = null;
list依然持有对str的引用,所以创建str时所开辟的内存空间是不会被回收的,这就是一个典型的“无意识的内存引用”。
为了防止这些“无意识的内存引用”,我们应该了解对象相互引用的时候是存在怎样的依赖关系的。
6、对象间相互依赖是怎样的
Object obj1 = new Object();
Object obj2 = obj1;
Object obj3 = obj2;
所以说下面这段代码,new 出来的那片内存空间是不会被回收的,因为obj2和obj3还在持有这片内存的引用。
Object obj1 = new Object();
Object obj2 = obj1;
Object obj3 = obj2;
object1 = null;
如何处理内存泄漏问题
对于长生命周期的变量,我们可以进行手动回收,或者使用弱引用。
总结:对于使用数组、集合元素的对象引用需要手动回收