内存泄漏几种常见的方式:
1. 无意识的对象保持。 就是接下来的例子。
2. 使用缓存。(很长一段时间仍然留在缓存中)
一旦你把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。对于这个问题,有几种可能的解决方案。如果你正好要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用WeakHashMap代表缓存;当缓存中的项过期之后,它们就会自动被删除。记住只有当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。
更为常见的情形则是,"缓存项的生命周期是否有意义"并不是很容易确定,随着时间的推移,其中的项会变得越来越没有价值。在这种情况下,缓存应该时不时地清除掉没用的项。这项清除工作可以由一个后台线程(可能是Timer或者ScheduledThreadPoolExecutor)来完成,或者也可以在给缓存添加新条目的时候顺便进行清理。LinkedHashMap类利用它的removeEldestEntry方法可以很容易地实现后一种方案。对于更加复杂的缓存,必须直接使用java.lang.ref。
3. 监听器和其他回调
如果你在实现的是客户端注册回调却没有显式地取消注册的API,除非你采取某些动作,否则它们就会积聚。确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用(weak
reference),例如,只将它们保存成WeakHashMap中的键。
对于 1.无意识的对象保持,代码:
1 public class Stack {
2 private Object[] elements;
3 private int size = 0;
4 private static final int DEFAULT_INITIAL_CAPACITY = 16;
5
6 public Stack() {
7 elements = new Object[DEFAULT_INITIAL_CAPACITY];
8 }
9
10 public void push(Object e) {
11 ensureCapacity();
12 elements[size++] = e;
13 }
14
15 public Object pop() {
16 if (size == 0)
17 throw new EmptyStackException();
18 return elements[--size];
19 }
20
21 /**
22 * * Ensure space for at least one more element, roughly* doubling the
23 * capacity each time the array needs to grow.
24 */
25 private void ensureCapacity() {
26 if (elements.length == size)
27 elements = Arrays.copyOf(elements, 2 * size + 1);
28 }
29 }
修改方式:
把上面的pop方法修改成如下:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
清空过期引用的另一个好处是,如果它们以后又被错误地解除引用,程序就会立即抛出NullPointerException异常,而不是悄悄地错误运行下去。尽快地检测出程序中的错误总是有益的。