【Effective Java 07】消除过期的对象引用
1. 消除因对象自己管理内存导致的内存泄漏
在支持垃圾回收的语言中,内存泄漏是非常隐蔽的(“无意识的对象保持”)。如果一个对象引用被无意识地保留起来了,那么垃圾回收机制不会处理这个对象,而且也不会处理被这个对象引用的所有其他对象。即使只有少量的几个对象引用被无意识地保留下来,也会有许许多多地对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。
下面的代码中,实现了一个简单的 Stack 容器,但是该容器在 pop 元素的时候仅仅将边界size减1。在此过程中,被“删掉”元素只是从 Stack 容器的接口上无法再访问,但是仍在 elements 数组中。因此,垃圾回收机制无法将其删除。导致内存泄漏。而解决办法就是在 pop 后将被删除元素的引用重置为空。
public class Stack {
private Object[] element;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
element = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
element[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
/*
* 此处, 存在内存泄露问题
* 在 pop 时仅仅是将 --size, 被删掉的对象只是逻辑上被删除了, 但是引用还在数组里
* 因此, 垃圾回收机制不会将其回收
*/
element[size] = null; // 不加会有内存泄漏
return element[--size];
}
private void ensureCapacity() {
if (element.length == size) {
element = Arrays.copyOf(element, size << 2 + 1);
}
}
}
2. 缓存导致的内存泄漏
一旦把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用后很长一段时间内仍然留在缓存中。对于这个问题,有几种解决方案:
- 在缓存中使用弱引用:使用诸如
WeakHashMap
作为缓存实现 - 使用一个额外线程,定时清除超时未访问的的缓存引用
3. 监听器和其他回调导致内存泄漏
如果你实现了一个 API,客户端在这个 API 中注册回调,却没有显示地注销回调,那么除非你采取某些动作,否则它们就会不断地堆积起来。确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用。例如,仅将他们保存为 WeakHashMap
中的键。
4. 主动检查内存泄漏
由于内存泄漏通常不会表现成明显的程序失败,所以它们可以在一个系统中存在很多年,往往只有通过仔细检查代码,或借助于 Heap 剖析工具(Heap Profiler)才能发现内存泄漏问题。最好,在内存泄漏前就知道这些将如何发生,并主动阻止它们发生。