06.消除过期的对象引用

06.消除过期的对象引用

概念

当你从手工管理内存的语言(比如C或者C++)转换到具有垃圾回收功能的语言的时候,程序猿的工作就会变得更加容易,
因为当你用完了对象之后,他们就会被自动回收。当你第一次经历对象回收功能的时候,会觉得这简直有点不可思议。
这很容易给你留下这样的印象,认为自己不再需要考虑内存管理的事情了,其实不然。

意图

解决内存泄露问题

案例

数组模拟栈简单功能案例

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

这个程序没有明显的错误(它的通用版本请见29项)。无论如何测试,它都会成功地通过每一项测试,但是这个程序中隐藏着一个问题。
简而言之,改程序存在“内存泄漏”,由于垃圾收集器的活动增加或者内存占用增加,程序性能的降低会逐渐表现出来。
在极端的情况下,这种内存泄漏会导致磁盘分页(Disk Paging),甚至导致程序失败并出现OutOfMemoryError,但这种失败情形相对比较少见。

那么,程序中哪里发生了内存泄漏呢?如果一个栈先是增长,然后再收缩,那么,从栈中弹出来的对象将不会被当做垃圾回收,
即使使用栈的程序不再引用这些对象,它们也不会回收,因为,栈内部维护着对这些对象的过期引用(obsolete references),
所谓的过期引用,是指永远也不会再被解除的引用。在本例中,凡是在element数组的“活动部分”(active portion)之外的任何引用都是过期的。
活动部分是指element中下标小于size的那些元素。

如果无意中保留了对象引用,则不仅将该对象从垃圾回收中排除,而且该对象引用的任何对象也是如此,
依此类推。即使无意中保留了少量对象引用,也会阻止许多对象被垃圾回收器收集,对性能可能产生很大影响。

这类问题的修复方法很简单:一旦对象引用已经过期,只需要清空这些引用即可。
对于上述例子中的Stack类而言,只要一个单元被弹出栈,指向它的引用就过期了,pop方法的修订版本如下所示:

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

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

总结

当程序员第一次被类似这样的问题困扰的时候,它们往往会过分小心:对于每一个对象的引用,一旦程序不再用到它,就把它清空。
其实这样做即没必要,也不是我们所期望的,因为这样做会把程序代码弄得很乱。
清空对象引用应该是一种例外,而不是一种规范行为。消除过期引用最好的方法是让包含该引用的变量结束其生命周期。
如果你是在最紧凑的作用域范围内定义每一个变量(第57项),这种情形就会自然而然地发生。

那么,何时应该清空引用呢?Stack类的哪方面特性使它易于遭受内存泄漏的影响呢?简而言之,问题在于,
Stack类自己管理内存(manage its own memory)、存储池(storage pool)包含了elements数组(对象引用单元,而不是对象本身)的元素。
数组活动区域(同前面的定义)中的元素是已分配的(allocated),而数组其余部分的元素则是自由的(free)。
但是垃圾回收器无法知道这一点;对于垃圾回收器而言,elements数组中的所有对象引用都同等有效。
只有程序猿知道数组的非活动部分是不重要的。程序猿可以把这个情况告知垃圾回收器,
做法很简单:一旦数组元素变成了非活动部分的一部分,程序猿就手动清空这些数组元素。
通常来说,只要类是自己管理内存,程序猿就应该警惕内存泄漏问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。
内存泄漏的另一个常见来源是缓存。一旦你把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它在很长一段时间没有使用,但是却仍然留在缓存中。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值