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

本文是《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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值