Java尽管采用自动的内存管理方式,但是仍然存在泄露的可能,我们知道JVM认为对象没有引用时,会把这个对象视为垃圾。
Java内存泄露就存在于,这个对象实际上已经不再需要,但是仍然存在引用,此时就会产生内存泄露。
其实java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。作者在书中提到了3个场景:
1.流失监听器问题,在awt、swing编程中,给组件添加了事件监听器,这些组件的生命周期如果很长的话,监听器对象将不能被正确回收。 关于GUI编程我不是很熟悉,这一点存有疑问,因为显然你触发一个按钮的事件,当然是一直期待同样的行为发生,如果删除了监听器或者使用弱引用让JVM回收不符合业务逻辑和用户体验。
2.集合类,集合类仅仅有添加元素的方法,而没有相应的删除机制,导致内存被占用。这一点其实也不明确,这个集合类如果仅仅是局部变量,根本不会造成内存泄露,在方法栈退出后就没有引用了会被jvm正常回收。 而如果这个集合类是全局性的变量(比如类中的静态属性,全局性的map等),那么没有相应的删除机制,很可能导致集合所占用的内存只增不减,因此提供这样的删除机制或者定期清除策略非常必要。
3.单例模式。 不正确使用单例模式是引起内存泄露的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被jvm正常回收,导致内存泄露。
常见的内存泄露代码如下:
1.Stack的内存泄露
下面是Stack的Pop方法,pop方法的作用是弹出栈最上面的对象,并释放这个对象的内存。
下面的代码可以看到,只是返回了栈最上面的对象,但是由于数组仍然对对象存在引用,因此这个对象不会被GC回收
public Object pop(){
if( size == 0)
throw new EmptyStackException();
return elements[--size];
}
看看java.util.Stack中的实现,
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
红色字体部分,实际上就是释放了数组对对象的引用,因而,避免了内存泄露。
2.