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(Object e){
        if(size == 0){
            throw new EmptyStackException();
        }
        return elements[--size];
    }

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

如果一个栈先是增长,然后再收缩,那么从栈中弹出来的对象不会当做垃圾回收,即使使用栈的程序不再引用这些对象,他们不会再回收,这是因为栈内部维护着对这些对象的过期引用,过期引用是指永远不会再被解除的引用。

如果一个对象引用被无意识地保留起来,那么垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象。即使只有少量的几个对象引用被无意识地保留下来,也许有许许多多的对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。

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

只要一个单元被弹出栈,指向它的引用对象就过期了,只需清空这些引用即可。清空过期引用另一个好处是,如果它们以后又被错误的解除引用,程序就会立即抛出NullPointerException,而不是悄悄的运行下去,尽快检查测出程序中的错误总是有益的。

一旦数组元素变成了非活动部分的一部分,程序员就手工清空这些数组元素。只要类是自己管理内存,程序员就应该警惕内存泄漏的问题。

内存泄漏的另一个常见来源是缓存。一旦你把对象引用放到缓存中,就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中,有几种解决方案,如果正好要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意思,那么就可以用WeakHashMap代表缓存;当缓存中的项过期之后他们就会自动被删除。记住只有所要缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。

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

说明:来源为《Effective Java》第二版

 posted on 2021-04-11 11:00  岂曰-无衣  阅读(66)  评论(0编辑  收藏  举报