java 虽然有垃圾回收机制,但是对于被引用的对象,就算我们已经不再使用,它的回收机制可能也不会进行回收,称之为内存泄漏。
书中有这样一个例子,先看代码:
public class Stack {
public Object[] elements;//原文是private,便于测试改成public
private int size = 0;
private static final int DEFAULT_INITIAL_CAPCITY = 10;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPCITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
代码很简单,入栈、出栈,程序并没有明显的错误,无论怎么测试,都能通过,但是这段程序有一个内存泄漏。
当入栈,然后出栈,从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,他们也不会被回收。这是因为栈内部维护着对这些对象的过期引用,在这个例子中,凡是在elements数组的“活动”部分以外的任何引用都是过期的,活动部分是指elements中下标小于size的那些元素。
不如我们进行简单测试:
public class Test {
public static Stack stack = new Stack();
public static void main(String[] args) {
try {
for (int i = 0; i <= 5; i++) {
String str = "effective " + i;
stack.push(str);
}
String a = new String((String) stack.pop());
System.out.println(a);
System.out.println((String) stack.elements[5]);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们入栈了0~5共六条string,然后出栈,打印如下:
effective 5
effective 5
理所当然,打印a就是这样,但是5已经出栈,第二次打印就不正常了。此时,要对Stack类进行修改:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
再进行同样的测试:
effective 5
null
例子中,只要一个单元被弹出栈,指向他的引用就已经过期,经过清空引用就可以避免后期的错误了。
那么,我们应该何时清空引用呢?一般而言,只要类是自己管理内存,像Stack类这样,垃圾回收器并不知道数组的非活动区域已经变得不重要的时候,程序猿就应该警惕内存泄漏问题。