《Effective Java 中文版 第2版》学习笔记 第6条:消除过期的对象引用

Posted 王景迁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Effective Java 中文版 第2版》学习笔记 第6条:消除过期的对象引用相关的知识,希望对你有一定的参考价值。

  内存泄漏的第一个常见来源是存在过期引用。

 1 import java.util.Arrays;
 2 import java.util.EmptyStackException;
 3 
 4 public class Stack {
 5     
 6     private Object[] elements;
 7     private int size = 0;
 8     private static final int DEFAULT_INITIAL_CAPACITY = 16;
 9     
10     public Stack() {
11         elements = new Object[DEFAULT_INITIAL_CAPACITY];
12     }
13     
14     private void ensureCapacity() {
15         if (elements.length == size) {
16             elements = Arrays.copyOf(elements, 2 * size + 1);
17         }
18     }
19     
20     public void push(Object e) {
21         ensureCapacity();
22         elements[size++] = e;
23     }
24     
25     public Object pop() {
26         if (size == 0) {
27         throw new EmptyStackException();
28     }
29         
30         return elements[--size];
31     }
32     
33 }

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

  如果一个对象引用被无意识地保留了,垃圾回收机制不仅不会回收这个对象,而且不会回收被这个对象所引用的所有其他对象。解决方法:一旦对象引用已经过期,只需清空这些引用即可。在本例中,只要一个元素被弹出栈,指向它的引用就过期了。修改如下:

 1 public Object pop() {
 2     if (size == 0) {
 3     throw new EmptyStackException();
 4     }
 5         
 6     Object result = elements[--size];
 7     elements[size] = null; // 清空引用
 8         
 9     return result;
10 }

  清空过期引用的另一个好处是,如果它们以后又被错误地解除引用,程序会立即抛出NullPointerException异常,而不是悄悄地错误运行下去。尽快地检测出程序中的错误总是有益的。消除过期引用最好的方法是让包含该引用的变量结束其生命周期。如果在最紧凑的作用域范围内定义每一个变量(见第45条),这种情形会自然而然地发生。

  只要类是自己管理内存,程序员就应该警惕内存泄漏问题。一旦元素被释放掉,该元素中包含的任何对象引用都应该被清空。

 

  内存泄漏的第二个常见来源是缓存。

  把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。用WeakHashMap代表缓存时,当缓存中的项过期之后,它们会被自动地删除。只有当缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才起作用。

  随着时间的推移,缓存项会变得越来越没有价值,缓存应该时不时地清除掉没用的项。可以由一个后台线程(可能是Timer或者ScheduledThreadPoolExecutor)来完成,或者在给缓存添加新条目时进行清理。LinkedHashMap类可以通过它的removeEldestEntry方法来实现后者。对于更加复杂的缓存,必须直接使用java.lang.ref。

 

  内存泄漏的第三个常见来源是监听器和其他回调。

  如果客户端在自己实现的API中注册回调,却没有显式地取消注册,那么除非自己采取某些动作,否则它们就会积聚。确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用(weak reference),例如,只将它们保存为WeakHashMap中的键。

 

  往往只有通过仔细检查代码,或者借助于Heap剖析工具(Heap Profiler)才能发现内存泄露问题。

 

  参考资料

  《Effective Java 中文版 第2版》 P21-23

以上是关于《Effective Java 中文版 第2版》学习笔记 第6条:消除过期的对象引用的主要内容,如果未能解决你的问题,请参考以下文章

《effective java》中文第2版 note

Effective Java中文版 第2版pdf

《Effective Java 中文版 第2版》学习笔记 第6条:消除过期的对象引用

Effective Java中文版(第2版) PDF分享下载

《Effective Java 中文版 第2版》学习笔记 第7条:避免使用终结方法

《Effective Java 中文版 第2版》学习笔记 第5条:避免创建不必要的对象