首先,什么是內存泄露?經常聽人談起內存泄露,但要問什么是內存泄露,沒幾個說得清楚。內存泄露是指無用對象(不再使用的對象)持續占有內存或無用對象的內存得不到及時釋放,從而造成的內存空間的浪費稱為內存泄露。內存泄露有時不嚴重且不易察覺,這樣開發者就不知道存在內存泄露,但有時也會很嚴重,會提示你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回收的。其實原因依然是長生命周期對象持有短生命周期對象的引用。
單例模式導致的內存泄露
單例模式,很多時候我們可以把它的生命周期與整個程序的生命周期看做差不多的,所以是一個長生命周期的對象。如果這個對象持有其他對象的引用,也很容易發生內存泄露。
內部類和外部模塊的引用
其實原理依然是一樣的,只是出現的方式不一樣而已。