你能看出以下代码哪里内存泄漏吗?
// 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()方法存在内存泄漏。
内存泄漏可以称为“ 无意识的对象保持(unintentional object retention)”。
在pop()方法中从栈中弹出来的对象将不会被当做垃圾回收。栈内部维护着对这些对象的过期引用(obsolete reference)。所谓的过期引用,是指永远也不会再被解除的引用。凡是在elements数组的“活动部分”(active portion)之外的任何引用都是过期的。活动部分是指elements中下标小于size的那些元素。
解决方法:上述述例子中的Stack类而言,只要一个单元被弹出栈,指向它的引用就过期了。一旦数组元素变成了非活动部分的一部分,就手工清空这些数组元素。修改后的pop()方法如下:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];//result相当于一个temp。
elements[size] = null; // Eliminate obsolete reference
return result;
}
清空对象引用应该是一种例外,而不是一种规范行为。消除过期引用最好的方法是让包含该引用的变量结束其生命周期。
延伸阅读
length、size()、length()的区别:
- 数组的长度(length):数组能容纳元素个数的值
- 泛型集合的大小(size()):泛型中元素的个数
- 字符串的长度(length()):字符的个数
前缀递减和后缀递增:
- 前缀递减,"--"操作符位于变量或表达式前,先执行运算,再生成值。如上例中
elements[--size]
,size大小先减1,所以Object result = elements[--size];
中result元素下标为size=size-1。 - 后缀递增,"++"操作符位于变量或表达式后,先生成值,再执行运算。如上例中
elements[size++] = e;
,元素下标为size,再执行运算size=size+1。
Arrays.copyOf()方法:
- a copy of the original array, truncated or padded with nulls to obtain the specified length(原始数组的副本,缩短或填补null来获取指定的长度)。
- 作用:如果数组元素的个数等于数组的长度,新建副本数组,将长度扩大为两倍加一,将数组副本赋值给elements。