ArrayList的层底是一个Object数组,当执行remove方法的时候,会把对应的元素从后往前移动一位,实现删除功能,代码如下:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
但在下面有一句非常关键的代码
elementData[--size] = null; // clear to let GC do its work
注释也说明了,将最后一位元素指定为null,让GC来回收,而这一位元素,可以称为过期对象,因为你的size已经执行--操作,你放在数组的最后一位,永远也不会再被使用,如果你不指定用null来进行回收,随着垃圾回收器活动的增加,或者由于不断的增加内存占用,程序性能的降低会逐渐的表现出来,在极端的情况,会出现内存泄露,当然这种情况确实少见。
下面再用一个进栈出栈的小例子来详细的解释一个过期对象的引用处理
public class Stack {
private Object[] elements;
private int size = 0;
public Stack(int capacity) {
this.elements = new Object[capacity];
}
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) {
Object[] oldElements = elements;
elements = new Object[2 * elements.length + 1];
System.arraycopy(oldElements, 0, elements, 0, size);
}
}
}
这是一段简单的进栈出栈代码,在pop()方法中,直接取最后一位元素,同时,记录数组的size也减了一,这时,同样如ArrayList一样,最后一位元素在方法中再也不会被引用,成为过期对象。如果不考虑GC的问题,上段代码你怎么测试,都会正常执行,但是我们不应该让自己的程序存在这样一个潜在的GC问题,应在POP方法中,如下解决
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
ArrayList的源码和上述的例子,都是因为如果不置为null, 栈内部维护着对这些对象的过期引用(absolete reference)。所谓的过期引用,是指永远也不会再被解禁的引用。凡是在elements数组的“活动区域(active portion)”之外的引用都是过期的,elements的活动区域是指下标小于size的那一部分。
Java内存泄漏是很隐蔽的,如果一个对象引用被无意识地人口负债期起来,那么GC不仅不会处理这个对象,而且也不会处理被这个对象引用的所有其它对象,即使只有少量的几个对象引用被无意识地保留下来,也会有很多的对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。