[Effective Java] Note: - Chapter-2-3:Destroy the Objects

 

虽然Java提供了无偿的垃圾回收机制。但这不代表我们从此完全不用操心对象的销毁。

消除过期对象的引用

Java虽然提供了自动垃圾回收,但还是可能因为过期对象导致内存泄漏。下面的Stack类中就隐藏着一个内存泄漏的隐患。因为pop()方法在返回对象,往回移动指针之后没有清除elements数组中的引用。

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];
    }

    private void ensureCapacity() {
        if (element.length = size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

所以正确的做法是,当一些对象过期之后,需要手动清空引用。尤其是 容器这种需要自己管理内存的类,尤其要注意

缓存是内存泄漏的另一常见来源

如果缓存中的对象不太重要,只有外部还在使用它(保持着对对象的引用),它才是有意义的。那么可以使用WeakHashMap来储存缓存。

WeakHashMap使用WeakReference来引用它的KeyWeakReference不能保护对象被垃圾回收清理。一旦某个Key被清理,WeakHashMap中这个Key值对应的Entry也被清理掉。

WeakReferenceSoftReference的区别是,SoftReference只在系统内存吃紧的时候才被释放。而WeakReference只要垃圾回收,就会被释放。

另外一种方案是利用一个Timer或者ScheduledThreadPoolExecutor后台线程来按时间清理过期对象。或者在每次给缓存添加新条目的时候清理一下缓存。

缓存的第三个常见来源是监听器和回调

避免使用finalize()方法

关于finalize()方法的定义是这样:它会在对象被垃圾回收之前被执行。 问题出在,JVM并不能保证垃圾收集执行的时间,我们也没办法控制。System.gc()System.runFinalization() 两个方法都只是 建议 虚拟机进行垃圾回收,但没有任何保障。

而且 finalize() 会导致性能的损耗。所以 一般情况不要用finalize()方法。只有在两种情况下,需要使用finalize()方法:

  1. finalize()方法在有其他显式终止方法的情况下,充当”Safety Net”的第二层保险。
  2. 用来终止本地资源。因为虚拟机无法回收本地资源。

用显式终止方法代替finalize()

需要正确关闭的资源可以自己写一个close()方法。比如说InputStreamOutputStreamjava.sql.Connection上的close()方法。一个惯用法是放在一个try-finally代码块中,以确保无论使用对象的时候是否抛出异常,close()始终能够执行。

Foo foo = new Foo();
try {
    // do something with foo
} finally {
    foo.close();
}