原文 http://blog.youkuaiyun.com/axman/archive/2009/09/22/4579260.aspx
Java 有内存泄漏吗?有。
最简单的 java 内存泄漏是一种数据结构的实现。比如 :
class InnerStack<E>{
private int size ;
private int INITIALCAPACITY = 32;
private E[] os ;
@SuppressWarnings ( "unchecked" )
public InnerStack(){
os = (E[]) new Object[ INITIALCAPACITY ];
}
public void push(E o){
checkCapacity();
this . os [ size ++] = o;
}
public E pop(){
if ( size <= 0) throw new EmptyStackException();
E o = (E) this . os [-- size ];
this . os [ size ] = null ; //1
return o;
}
public E peek(){
if ( size <= 0) throw new EmptyStackException();
E o = (E) this . os [ this . size -1];
return o;
}
@SuppressWarnings ( "unchecked" )
private void checkCapacity(){
if ( size == os . length ){
os = (E[]) new Object[ size * 2 + 1];
}
}
}
在 JAVA 中 99.9% 的情况下,我们不需要写 type var = null; 这种语法,但在自己实现的数据结构中,如果上例注释 1 处 os[10] 指向的对象被 pop 出去,调用的人使用完了当然的想法是希望它能被系统回收,但是由于 InnerStack 中还有一个内部数组中有一个引用指向它,这个对象被不能在调用者使用完后立即标记为可回收。
如果同样的方法实现循环队列,情况还有些好转,即使没有打断引用,当入队的元素多于数据长度时原来的引用就会被后来入队的引用覆盖掉。但对于自动扩展的栈来说,如果某一时刻容量到了一个峰值,比如底层数组长被扩展到 65 ,在 os[64] 如果 pop 出去后没有设置
os[64] = null; 那么以后在很长时间不会再访问到这个位置(峰值嘛)那么它指向的对象虽然在外部已经“使用”完成了,但却无法被回收,别小看这几个对象,因为它们还会引用别的对象。关系很复杂,可能会造成很大的内存泄漏。
同理如果底层是其它的数据结构,比如 List 等对象支持,如果对象被获取后,底层的对象容器是否及时地 remove, 是否调用 clear ,都会造成对象被无意地引用而不能被回收。需要把一些对象先缓存起来然后再获取使用的时候,最好是能选择象 WeakHashMap 这样的弱引用数据结构,以便对象在外部获取后能尽快地回收。
Effective Java 在提到其它情况的内存泄漏时提到了 Listeners 和 callbacks, 其实这些都不是主要的,因为一个 Listener 被 add 后,你并不能确定应该在什么时候应该被清除,因为绝大多数 Listener 是和应用的生命周期相同,除非你在退出之前的逻辑中处理它们,否则它们没有被回收的理由。但是真正引起大量的内存泄漏的实际应用中, NIO 的使用一定要非常非常的小心。