首先,什么是内存泄露?经常听人谈起内存泄露,但要问什么是内存泄露,没几个说得清楚。内存泄露是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory(内存泄漏)。
内存泄漏的原因
在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上。
Object object1 = new Object();//obj1
Object object2 = new Object();//obj2
object2 = object1;
//...此时,obj2是可以被清理的
new 出来的对象(new Object())会存在堆内存中,而object1这个是堆里对象的引用,存在于栈上,指向堆中的对象obj1;但是当堆中的对象没有引用指向时,垃圾回收就可以将它的内存收走。
所以object2指向的堆对象是可以被回收(清理)的。因为没有object1和object2都没有指向它。
内存泄漏就是说堆上的对象得不到及时的回收,导致内存泄漏。
举个栗子:
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他代码
}
}
这里的object实例,其实我们期望它只作用于method1()方法中,且其他地方不会再用到它,但是,当method1()方法执行完成后,object对象所分配的内存不会马上被认为是可以被释放的对象,只有在Simple类创建的对象被释放后才会被释放。
这就是一种内存泄露。解决方法就是将object作为method1()方法中的局部变量,当然,如果一定要这么写,可以改为这样:
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他代码
object = null;
}
}
这样,之前“new Object()”分配的内存,就可以被GC回收。
一些容易发生内存泄露的例子和解决方法
静态类/变量引起内存泄露
像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
容器使用时的内存泄露
查看集合源码,ArrayList的remove方法
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
各种有close()方法的对象
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,以及使用其他框架的时候,除非其显式的调用了其close()方法(或类似方法)将其连接关闭,否则是不会自动被GC回收的。其实原因依然是长生命周期对象持有短生命周期对象的引用。
单例模式导致的内存泄露
单例模式,很多时候我们可以把它的生命周期与整个程序的生命周期看做差不多的,所以是一个长生命周期的对象。如果这个对象持有其他对象的引用,也很容易发生内存泄露。
内部类和外部模块的引用
其实原理依然是一样的,只是出现的方式不一样而已。