在使用java自动的垃圾机制后, 有时可能会松懈。 认为不需要关注对象的生命周期。如下面的代码示例将会导致内存泄露的情况。
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()方法在元素弹出后,并未对elements[index]下标的引用进行消除, 将导致存在引用关系。 此时jvm gc将不能对其进行回收, 从而导致内存泄露。
改进的方式如下:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
通过null来实现消除一些过期对象的引用关系很容易被滥用, 如很多c/c++的程序员转到java会习惯性在对象使用之后将引用赋值为null。所以利用null来消除对象的引用应该作为一种例外的方式,而不是一种规范。
那什么时候需要考虑出现这种内存泄露的情况呢?
每当需要自己管理内存的时候,编程是需要考虑内存泄露的情况
几个内存泄露的示例
-
缓存 有时候在程序中对一些对象进行缓存, 很容易忘记消除一些无效对象的引用并造成内存泄露。可以通过如:定时扫描无效的对象,或者在插入,更新,删除的方法中做检查。
-
回调监听 实现一些api注册了回调而没有实现注销,如打开了一些资源忘记释放,在程序不断执行这种问题累计之后将导致崩溃。 需要形成一种好的习惯就是一旦使用资源就需要考虑如何释放。
总结
内存泄露有时候表现并不明显, 出现问题时可能并不是发生现场。 所以在编写代码是需要仔细检查,或者后面通过一些堆分析工具进行检查。