Tip:导致内存泄露的几个案例

214 阅读2分钟

在使用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来消除对象的引用应该作为一种例外的方式,而不是一种规范。

那什么时候需要考虑出现这种内存泄露的情况呢?

每当需要自己管理内存的时候,编程是需要考虑内存泄露的情况

几个内存泄露的示例

  1. 缓存 有时候在程序中对一些对象进行缓存, 很容易忘记消除一些无效对象的引用并造成内存泄露。可以通过如:定时扫描无效的对象,或者在插入,更新,删除的方法中做检查。

  2. 回调监听 实现一些api注册了回调而没有实现注销,如打开了一些资源忘记释放,在程序不断执行这种问题累计之后将导致崩溃。 需要形成一种好的习惯就是一旦使用资源就需要考虑如何释放。

总结

内存泄露有时候表现并不明显, 出现问题时可能并不是发生现场。 所以在编写代码是需要仔细检查,或者后面通过一些堆分析工具进行检查。