Java有内存泄漏吗?有。虽然有人说这个说法不准确,但是在C/C++程序中,我们把由当前进程开辟但当前进程在逻辑上却无法再管理的那些内存称为被进程泄漏的内存。事实上java同样会有这样的情况。
当我们最先接触java时就因为它自动管理内存不需要程序员手工干预而带来的方便性的原因喜欢上了它,但这个自动并不是全能的。对于一些隐性引用所引起的内存泄漏,有时很长时间甚至几个月,几年我们也很难发现,除非是非常有经验的人去仔细地查看源码,借用heap分析工具细致地分析才能发现。
最简单的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的使用一定要非常非常的小心。
在很多时间,NIO的一个IO通道一次不能读取完成完成所有传输的数据,那么我们无论把已经有的数据缓在自己的数据结构中和SelectorKey对应,还是把数据attach到SelectorKey上,始终有个风险,就是那个Key对应的客户端有可能在非常恶劣的网络下或干脆断线,你所注册的事件根本就没有返回结果,则保存原来的数据永远没法处理。对于写出操作同样有这样的问题。所以一定要在把开始注册的时间和Selector已经处理过的次数attach到Key中然后每次处理的时候判断一下,对于经过多次处理以及超过一定时间没有读取完成你要的数据可以做相应的处理,及时把你处理这个连结的数据结构释放出来。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/axman/archive/2009/09/22/4579260.aspx