【Effective Java】条6:消除过期对象引用

当你从内存需要管理的语言(CC++),跳转到基于GC管理内存的语言时,你会发现要简单很多。因为GC会自动回收不可用对象,它释放了你的工作。

但是GC管理内存一定可靠吗?答案是未必。如:

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

以上是一个简单的Stack模拟。如果你测试,你会发现任何操作都是可以的。但是却有个不足:当取出栈顶数据的时候,栈顶数据未清空,使得GC不能够回收栈顶空间。如此反复插入、取数据,最终可能导致OutOfMemoryError

如何修改呢?

很简单,将栈顶元素置空即可。即:

public Object pop() {
    if (size == 0)
        throw new EmptyStackException();

    Object result = elements[--size];
    //消除过期对象引用
    elements[size] = null;
    return result;
}

那是不是有点疑惑?以后我们在开发中是不是都需要将无需使用的对象置空?答案是否。置空对象应该是例外情况,而不能作为规范,否则GC就是多余的了。

其实,当类需要自己管理本身的内存时,才需要消除过期对象的引用。开发者应该警惕内存泄漏问题。通常这样的类常常与数组相关联,开始时数组中都存储了数据,之后取出(不可用),但是GC并不明白此时的取出操作,因为数据还是存储在内存中的,只有开发者知道此时的取出就是使数据不可用,所以开发者需要给GC提示。

内存泄漏的另一常见来源是缓存。因为一旦你将数据存储在缓存中,而没有及时的进行清理,那就相当于内存泄漏。

监听器和回调也是导致内存泄漏的原因。如果有个API,客户端在向其注入回调,但没有显示的取消注册,那除非你采用了某些动作取消,否则会一直积聚直至内存溢出为止。

在基于GC的语言中,内存泄漏是很难被发觉的。平时只能依靠仔细检查代码,或借助于Heap Profiler工具进行检查。

猜你喜欢

转载自blog.csdn.net/xl890727/article/details/80200955