6. 【创建和销毁对象】消除过期的对象引用

来源:互联网 发布:linux dhcp设置dns 编辑:程序博客网 时间:2024/06/07 17:48

本文是《Effective Java》读书笔记第6条,其中内容可能会结合实际应用情况或参考其他资料进行补充或调整。


我们都知道,Java语言的优势之一就是具有垃圾回收技能,这一点尤其是对一些从C或C++语言转过来的程序猿来说感觉尤其方便,仿佛使用Java就不需要考虑内存管理的事情了,真的是这样吗?

无意识的对象保持

先看下边的代码,这段代码用于实现一个简单的栈:

public class MyStack {    private Object[] elements;    private int size = 0;    private static final int DEFAULT_INITIAL_CAPACITY = 16;    public MyStack() {        elements = new Object[DEFAULT_INITIAL_CAPACITY];    }    public void ensureCapacity() {        if (elements.length == size) {            elements = Arrays.copyOf(elements, 2 * size + 1);        }    }    public void push(Object o) {        ensureCapacity();        elements[size++] = o;    }    public Object pop() {        if (size == 0) {            throw new EmptyStackException();        }        return elements[--size];    }}

程序中并没有明显的错误,测试可用,但其实程序中存在内存泄漏。
当栈的长度增长由收缩后,那么从栈中pop出的对象不会被当做垃圾回收,而实际上根据栈的使用特点,这些对象在使用之后就不会再被引用了。
这是因为栈内部维持着对这些对象的过期引用(指永远不会再被解除的引用)。
这里写图片描述
有时候内存泄漏是很隐蔽的,假设过期引用指向的对象内还含有更多的过期引用,就会形成链状或树状引用,这些引用指向的对象同样不会被垃圾回收,从而影响性能。
这类问题的修复方法很简单:一旦对象引用过期,只需清空这些引用即可。不多解释,下边代码中找吧:

    public Object pop() {        if (size == 0) {            throw new EmptyStackException();        }        Object result = elements[--size];        elements[size] = null;    // 清除引用        return result;    }

清空过期引用还有个好处,就是如果它以后又被错误地解除引用,程序就会立即抛出空指针异常,而不是悄悄地错误运行下去。尽快得检查出程序中的错误总是有益的。

清除对象引用应该是一种例外,而不是一种规范行为。以上内容并不是让您一旦不再使用某个对象,就立马清除其引用,这样做既没有必要也不是我们所期望的。最好的方法是让包含该引用的变量结束其生命周期。如果您是在最紧凑的作用域范围内定义每个变量,这种情况就会自然而然地发生。那MyStack类的哪些特性导致了它易于内存泄漏呢?问题在于MyStack类自己管理内存,也就是elements数组,这种情况下就需要额外关注。

缓存

内存泄漏的另一个常见来源是缓存。一旦您把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用但仍然留在缓存中。
如果您正要实现这样的缓存,可以用WeakHashMap代表缓存,当缓存中的项过期后,它们就会自动删除。若要使某缓存项有意义,只要在缓存之外保存对这个项的键的引用即可。
更为常见的情形则是,缓存项的生命周期是否有意义并不是很确定,随着时间的推移,其中的项会变得原来越没有价值。这时候利用LinkedHashMap类的removeEldestEntry方法可以很容易实现。对于更复杂的缓存,必须直接使用java.lang.ref。

java.lang.ref包提供了与Java垃圾回收器密切相关的引用类,包括我们日常使用的强类型,刚才提到的弱引用,以及能够根据JVM的内存状况“智能”处理引用清理的软引用等,具体请见深入探讨java.lang.ref。

监听器及回调

内存泄漏的第三个常见来源是监听器和其他回调。如果您实现了一个API,客户端在这个API中注册回调,却没有显式地取消注册,那么除非您采取某些动作,否则它们就会积累。确保回调立即被当成垃圾回收的最佳方法是只保存它们的弱引用,例如,只将它们保存成WeakHashMap中的键。

最后

由于内存泄漏通常不会有明显的错误或异常,所以可以在一个系统中存在多年。往往只有通过仔细的代码检查,或是借助Heap剖析工具才能发现内存泄漏问题。因此,如果能够在内存泄漏发生之前就知道如何预测此类问题,并阻止它们发生,那就再好不过了。

0 0
原创粉丝点击