Effective Java的第6条中提到,以下程序可能会引起内存泄露(原文引用:随着垃圾回收器活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来。在极端的情况下,这种内存泄漏会导致磁盘分页,甚至导致程序失败,而出现OutOfMemoryError错误,但是这种失败情形相对比较少见):
// Can you spot the "memory leak"?
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);
}
}泄露处在pop()函数中,--size后会导致大于size位置的elements依然被引用(即过期对象),从而不会被GC释放,解决方案是以下修改后的pop()函数:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}我的问题是,原程序的pop()会有一些泄露,但是应该只是很小的部分,也不会连续泄露吧?
因为push()函数被调用的时候,会覆盖之前的原过期对象引用,那么原过期对象不就会被GC了么?
比如:
public void caller()
{
Stack stack = new Stack();
stack.push(objectA);  //stack.elements[0]引用objectA, stack.size==1
stack.push(objectB);  //stack.elements[1]引用objectB, stack.size==2
stack.push(objectC);  //stack.elements[2]引用objectC, stack.size==3

stack.pop();  //stack.elements[2]依然引用到ObjectC,但是stack.size==2,导致文中提到的ObjectC成为过期对象,不能被GC
stack.pop();  //stack.elements[2]依然引用到ObjectB,但是stack.size==1,导致文中提到的ObjectB成为过期对象,不能被GC

stack.push(objectD); //stack.elements[1]引用objectD, stack.size==2,此时stack.elements[1]不再引用ObjectC,所以ObjectC应该可以被GC了吧?
stack.push(objecE);  //stack.elements[2]引用objectE, stack.size==3, 此时stack.elements[2]不再引用ObjectD,所以ObjectD应该可以被GC了吧?
}如上所述,我的迷惑是因为我对GC原理不是很清楚,在我看来push()被调用时,stack.elements[1]和stack.elements[2]已经被替换为ObjectD和ObjectE,所以ObjectB和ObjectC已经没被引用,可被GC了。不知我的理解哪里有误,JAVA基础不好,还请多多指教。

解决方案 »

  1.   

    嗯,那么什么情况下会发生泄漏呢?
    难道在push()了一万次,再pop()了9999次后,一直保留这个stack,算泄漏了?
      

  2.   

    这就是leak。
    没有用的对象没有理由放在内存。
      

  3.   

    你的理解我觉得很精确!
    pop完了再push覆盖原有的数组单元对象引用,原有对象引用没有其他地方引用,可以被GC。push()了一万次,再pop()了9999次后,没有清楚9999个数组中对象引用,会有leak的。
      

  4.   

    当然如果stack本身都没有被引用,我认为里面引用的对象也会被GC(至少应该被GC
      

  5.   

    嗯,多谢各位的解答!
    可能因为我是从C++转过来的,所以“泄漏”一词在意识中就是内存没有方法再被回收,当看到push()后,原对象内存可以被GC,就认为这不算泄漏。
    C++中没有delete的对象且没有指针再指向它的时候,该块内存就彻底“遗失”了。像上面的例子,pop()时忘记delete element[size],如果再push()一下,那么原element[size]指向的对象内存就再没机会回收了。想了一想,总结一下,打个稀烂的比喻:
    JAVA的内存泄漏,就像,占着茅坑不拉屎,虽然暂时没法用,但茅坑还在;
    C++的内存泄露,就像,彻底把茅坑用砖给埋了,我用不到,你也甭想用。
      

  6.   

    只有当对象变的不可到达的时候,该对象才会被回收。
    你有没有想过 多了这句 element[size] = null  以后,垃圾回收器就能将谁回收了?
    要明确:此处的stack 是用数组实现的,数组本身无论有没有值都不会被回收,
    能不能被回收指的是数组里面的装的对象
    这些对象一旦不能被回收,就回有一系列的链锁反应,内存很容易崩了。