问题
过期引用(obsolete reference)是指永远不会再被解除的引用,由于JVM有垃圾回收机制,如果存在过期引用,被引用的对象不会被回收掉,容易造成内存泄漏。那么,常见的内存泄漏有哪几种情况?
解决
在使用Stack,List等“数据容器”的数据结构时,应该时刻关注容器中的数据元素是否有存在内存泄漏的情况,比如,下例中:
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() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } /** * 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); } }
如果一个stack先增后减,那么会弹出的对象并不会被当成垃圾回收,原因在于stack内部维持着对这些对象的过期引用(在这里是数组的原因),这样就造成了内存泄漏。因此,在弹出数据元素的时,最好是释放元素引用,如:
Object obj = elements[--size]; elements[size] = null
内存泄漏另一个常见来源是缓存:一旦把对象引用放到了缓存,当很长一段时间没有使用之后仍然存在于缓存之中。如果在缓存之外存在对某个数据项的键的引用的话,该缓存项才有意义。这样的场景是适合用WeakHashMap。
内存泄漏第三个常见来源是监听器和回调:如果注册了回调,并没有显式的进行注销,也会造成内存泄漏的问题。确保回调会被正常的垃圾回收,只需要保存他们的弱引用即可,例如,只将它们保存为WeakHashMap中的键。
结论
如果代码中存在过期引用,很容易造成内存泄漏,甚至会导致系统崩溃,在实际编码过程中需要仔细检查代码,或者借助于Heap工具去检查内存泄漏的问题。