学习effective java-6创建和销毁对象之消除过时的对象引用

 该知识点是自己从书籍中学习的笔记。

定义

  过时的引用是这样的一个引用,该引用永远不会被解除。按照这种定义的话,超出元素数组中的活动区域的引用都是过时的。

一个内存泄露的例子:

public class StackTest {

    public Object[] elements;

    public int size = 0;

    private static final int DEFAULT_INITIAL_CAPACITY = 16;

 

    public StackTest() {

       elements = new Object[DEFAULT_INITIAL_CAPACITY];

    }

 

    public void push(Object e) {

       ensureCapacity();

       elements[size++] = e;

    }

 

    public Object pop() {

       if (size == 0)

           throw new EmptyStackException();

       Object o =  elements[--size];

       //elements[size] = null;//消除过时的对象引用方方案

       return o;

    }

 

    /**

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

    }

    public static void main(String[] args) {

       StackTest st = new StackTest();

       st.push("1");

       st.push("2");

       st.pop();

       st.pop();

       for(Object temp : st.elements){

           if(temp != null)

           System.out.println((String)temp);

       }

    }

}

  在未采用“消除过时的对象引用”时,打印结果:

  1

  2

这不符合我们想要的结果,因为对象已经pop了,那么StackTest应该不存在对象了。但是存在了对象,说明内存泄露。解决的方法就是:“  

public Object pop() {

       if (size == 0)

           throw new EmptyStackException();

       Object o =  elements[--size];

       elements[size] = null;//消除过时的对象引用方案

       return o;

    }

”,再次运行就正常了。

由上可知,消除过时的对象引用,指将对象引用设置为null,让该引用指向的对象游离在heap中,然后jvm会自动回收这些对象垃圾。Java虽然是可以自动回收垃圾,但是回收还是需要我们考虑。

尽管有很少部分的对象引用被保留了下来,但是将会导致有更多的对象将不会被垃圾回收。

消除过时的对象引用的好处

 1. 如果在随后,它们又被错误的解除引用的话,程序就会抛出一个NullPointerException,这种方式比默默地执行错误的事情好。可以越早检查出错误。

 2. 并不是说一旦对象引用使用完毕后,就消除这些过时的引用。消除过时的对象引用是一种例外而不是规范。使用消除过时的对象引用最好方法是包含引用的变量结束周期,最好是在最狭窄的范围内结束。

 3.可以减轻jvm的内存使用情况。

 4.提高性能。

什么时候清空对象引用?

1.  当一个类自己管理内存时候,我们就应该警惕内存泄露。

2.  内存泄露还有就是代码使用了缓存。如果将一个对象引用放入缓存中的话,那么随着时间的延长,估计人都会忘记它,然后缓存中的该对象引用就变成了不相干。当然这种问题有解决方法:

a.  使用WeakHashMap来作为缓存,当缓存中的实体变成过时时,实体将被自动移除。当然WeakHashMap仅仅有用的是:缓存的实体希望的生命周期是由被引用的key决定的,而不是value。

b.  使用后台线程(a Timer or ScheduledThreadPoolExecutor)来清理缓存中不使用的实体或者添加一个具有自己处理旧对象的副作用的东西来作为缓存(比如LinkedHashMap可以作为缓存,因为它的removeEldestEntry()方法,在每次put、putAll的时候,都会先清理最老的实体)。

c.  对于更复杂的缓存,你可以直接使用反射。

3.  具有监听和回调方法的代码可能造成内存泄露。

如果你实现了一个用户注册回调方法的API,但是没有明确撤销注册的话,估计会造成内存泄露。可以通过这个方法解决:使用WeakHashMap的key来存储这些不用的引用。

总结

  内存泄露是一个长期遗留下来的问题,内存泄露不像错误那么明显,基本是只有通过运行的结果才能够知道。目前可以通过代码review和一些工具(heap profiler)来分析可能出现的问题,来确保不发生一些问题,这些问题不是百分之百可以发现的,所以需要加强对代码的设计和理解。

 

发布了42 篇原创文章 · 获赞 0 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章