参考地址:https://blog.csdn.net/u014723123/article/details/33320671
过期引用(obsolete reference):指永远不会再被解除的引用。
比如下面的代码就存在这种风险(内存泄漏)
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); } }
这段程序中的错误很隐秘,无论怎么测试它都会成功。但是它的确存在着"内存泄露"的隐患,随着垃圾回收器活动的增加,这种内存泄露会导致磁盘交换,甚至导致程序失败(OutOfMemoryError错误),但是比较少见。问题在哪?如果一个stack先增后减,那么会弹出的对象并不会被当成垃圾回收,原因在于stack内部维持着对这些对象的过期引用(在这里是数组的原因),在上例中,凡是在elements数组的“活动部分”之外的任何引用都是过期的,所谓活动部分是指elements中下标小于size的那些元素。
在支持垃圾回收的语言里,内存泄漏是很隐蔽的,如果一个对象引用被无意识的保留了起来,那么,垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象,即使只有少量的引用被无意识的保留了下来,也会有许许多多的对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。
这类问题有一个很简单的修复方法,就是一旦对象引用过期,就将其清空。如下
public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null // Eliminate obsolete reference return result; }
清除过期引用的一个额外的好处是如果它们以后又被错误的解除引用,程序立即会抛出NullPointerException。
既然存在上面所说的问题,那么是不是要对每一个对象引用都在程序不再用到它的时候手到将引用清空呢?这样其实没什么必要,因为它会把代码弄的很乱,“清空对象引用应该是一种例外,而不是一种规范行为”。消除过期引用的最好方法是让包含该引用的变量结束其生命周期。那么,应该在何时清空引用呢?在如上的例子中,由于stack自己维持着存储池(数组),所以垃圾回收器不会将过期的引用当做垃圾,因为它认为数组中的所有对象引用都同等有效,那么在这种情况下,只有程序员知道哪些是有效的,哪些是过期的,就需要手工清空这些数组元素。一般来言,只要类是自己管理内存的,程序员就需要警惕内存泄漏的问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。