内存泄露是一个很不容易发现的问题,在很多情况下,我们都很容易忽略,Java的垃圾回收确实帮助我们解决了不少内存管理的问题,但是,这并不意味着我们就可以完全依赖Java的垃圾回收。我们还是在编写程序的时候需要考虑内存管理的问题。下面我们先来看一个例子,
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
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();
return elements[--size];
}
/**
* Ensure space for at least one more element, roughly doubling the capacity
* each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
但是这个程序中隐藏着一个问题。不严格地讲,这段程序有一个"内存泄漏(Memory Leak)",随着垃圾回收器活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来。如果一个栈先是增长,然后再收缩,那么,从栈中弹出来的对象将不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。这是因为,栈内部维护着对这些对象的过期引用(obsolete reference)。所谓的过期引用,是指永远也不会再被解除的引用。
这类问题的修复方法很简单:一旦对象引用已经过期,只需清空这些引用即可。对于上述例子中的Stack类而言,只要一个单元被弹出栈,指向它的引用就过期了。pop方法的修订版本如下所示:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。
内内存泄露的另一个常见来源是缓存,因此这时要用一个线程定期清缓存或在加入时清最少使用的缓存对象。在1.4发行版中,可以用java.util.LinkedHashMap的revmoveEldestEntry方法来实现后一方案。