[Effective Java] (06)消除过期的对象引用

2017-12-31  本文已影响0人  QyQiaoo

虽然在Java语言中拥有垃圾收集(GC)回收程序,在创建对象后,不用程序员手动回收对象,但在某些情况下依然会造成内存泄漏的情况。

在支持垃圾回收的语言中,内存泄漏是很隐蔽,也可以称这类内存泄漏为“无意识的对象保留”更为恰当。如果一个对象引用被无意识地保留起来,那么来及回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象,即使只有少量的几个对象引用被无意识地保留下来,也会有许许多多的对象呗排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。

内存泄漏主要有如下三种方式:

1. 过期的对象引用引起的内存泄漏

示例代码如下:

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 obj) {
        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 tha capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if(elements.length == size) {
            elements = Arrays.copyOf(elements,  2 * size + 1);
        }
    }
}

分析:如果一个栈先增长,然后在收缩,那么,从栈中弹出来的对象将不会被当作垃圾回收,即使使用栈的程序不在引用这些对象,它们也不会被回收,因为栈内部维护着对这些对象的过期引用(obsolete reference)。

过期引用:永远不会再被解除的引用,在上面代码中,凡是在elements数组的“活动部分(active portion)”之外的任何引用都是过期的。活动部分是指elements中下标小于size的那些元素。

解决办法:如下:一旦对象的引用已过期,清空这些引用。这样的一个好处是,如果它们以后又被错误的引用,程序就会立即抛出NullPointerException异常(尽快地检测出程序中的错误总是有益的)。

public Object pop() {
    if(size == 0) {
        throw new EmptyStackException();
    }
    Object res = elements[--size];
    elements[size] = null;    //Eliminate obsolete reference
    return res;
}

注:清空对象引用应该是一种例外,而不是一种规范行为。

2. 缓存中的对象引起的内存泄漏

内存泄漏的另一个常见的来源是缓存:对象应用存放在缓存中,当对象不再被使用时,很容易被遗忘掉而没有清理,于是,该对象引用会一直保存在缓存中,而你在逻辑上已经没有使用该对象,但该对象不会被GC回收,因为仍然有引用指向它。

解决办法:知道什么时候,缓存中的引用对象不再有用,有意义,在这个时候,就可以清理掉缓存中的对象引用。

3. 监听器和其他回调引起的内存泄漏

内存泄漏的第三个常见来源是监听器和其他回调:我们实现了一个API,用户在这个API中注册了回调,却没有显示地曲线注册,除非采取某些方法,否则他们就会积聚,确保回调立即被当作垃圾回收的最佳方法就是保存它的弱引用(weak reference),如:只把它们保存成WeakHashMap中的键。
(代码示例,待续···)

上一篇 下一篇

猜你喜欢

热点阅读