Java的垃圾回收确实帮助我们解决了不少内存管理的问题,但是,这并不意味着我们就可以完全依赖Java的垃圾回收。我们还是在编写程序的时候需要考虑内存管理的问题,例如:
class Stack
{
public Object[] elements;
//原文为private,但这里改为public为了测试下面代码
public int size=0;
public Stack(int initialCapacity)
{
this.elements=new Object[initialCapacity];
}
public void push(Object e)
{
ensureCapacity();
elements[size++]=e;
}
public Object pop() throws Exception
{
if(size==0)
throw new Exception();
return elements[--size];
}
private void ensureCapacity()
{
if(elements.length==size)
{
Object[] oldElements=elements;
elements=new Object[2*elements.length+1];
System.arraycopy(oldElements,0,elements,0,size);
}
}
}
上面的这个程序有一个“内存泄漏”问题,最终会导致程序失败(OutOfMemoryError错误)。如果栈先是增长,然后收缩,那么栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的客户程序不再引用这些对象,它们也不会被垃圾回收。因为栈内部维护着对这些对象的过期引用(即永远也不会再被解除的引用)。在上面的代码中,凡是在elements数组的“活动区域”之外的引用都是过期的,elements的活动区域是指下标小于size的那一部分。
举例来说,我们向栈中push进了10个对象,那么现在size的值为10;当我们弹出一个对象的时候,--size为9,也就是Object[9]中引用的对象将被弹出;但是问题出来了,当Object[9]引用的对象被弹出后,该对象所对应的位置的对象不会被回收,例如:
public class stackTest
{
public static Stack stack=new Stack(20);
public static void main(String[] args)
{
try
{
for (int i=0;i<=9;i++)
{
String a="Hello "+i;
stack.push(a);
}
String b=new String((String)stack.pop());
System.out.println(b);
System.out.println((String)stack.elements[9]);
}
catch(Exception e)
{
e.printStackTrace();
}
System.out.println((String)stack.elements[9]);
}
}
运行程序发现打印三个Hello 9,栈中元素的弹出并没有消除对栈中对象的引用。
对于垃圾回收器来说,它所管理的是elements数组的内存空间,当我们从栈中pop出元素时,对于垃圾回收器来说,element数组依然存在,它不会单独收回elements数组中的一个元素,elememts数组中的对象是由Stack来管理的,不受垃圾回收器的作用。
解决方法:一旦对象引用已经过期,只需清空这些引用即可。pop方法的修订版本如下:
public Object pop() throws Exception
{
if(size==0)
throw new Exception();
Object result=elements[size--];
elements[size]=null;
return result;
}