Java中的內存泄露,廣義並通俗的說,就是:再也不會被使用的對象的內存不能被回收,就是內存泄露。程序員
Java中的內存泄露與C++中的表現有所不一樣。在C++中,全部被分配了內存的對象,再也不使用後,都必須程序員手動的釋放他們。因此,每一個類,都會含有一個析構函數,做用就是完成清理工做,若是咱們忘記了某些對象的釋放,就會形成內存泄露。編程
可是在Java中,咱們不用(也沒辦法)本身釋放內存,無用的對象由GC自動清理,這也極大的簡化了咱們的編程工做。但,實際有時候一些再也不會被使用的對象,在GC看來不能被釋放,就會形成內存泄露。咱們知道,對象都是有生命週期的,有的長,有的短,若是長生命週期的對象持有短生命週期的引用,就極可能會出現內存泄露。咱們舉一個簡單的例子:數組
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; } }
substring(int beginIndex, int endndex )是String類的一個方法,可是這個方法在JDK6和JDK7中的實現是徹底不一樣的(雖然它們都達到了一樣的效果)。在JDK1.6中不當使用substring會致使嚴重的內存泄漏問題。性能
substring(int beginIndex, int endIndex)方法返回一個子字符串,從父字符串的beginIndex開始,結束於endindex-1。父字符串的下標從0開始,子字符串包含beginIndex而不包含endIndex。this
String x = "abcdef"; x = x.substring(1,3); System.out.println(x); //output: bc
String類是不可變變,當上述第二句中x被從新賦值的時候,它會指向一個新的字符串對象。code
String對象被看成一個char數組來存儲,在String類中有3個域:char[] value、int offset、int count,分別用來存儲真實的字符數組,數組的起始位置,String的字符數。由這3個變量就能夠決定一個字符串。當substring方法被調用的時候,它會建立一個新的字符串,可是上述的char數組value仍然會使用原來父數組的那個value。父數組和子數組的惟一差異就是count和offset的值不同。對象
JDK6中substring的實現源碼:生命週期
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); //使用的是和父字符串同一個char數組value } public String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }
由此引起的內存泄漏泄漏狀況:內存
String str = "abcdefghijklmnopqrst"; String sub = str.substring(1, 3); str = null;
假如上述這段程序在JDK1.6中運行,咱們知道數組的內存空間分配是在堆上進行的,那麼sub和str的內部char數組value是公用了同一個,也就是上述有字符a~字符t組成的char數組,str和sub惟一的差異就是在數組中其實beginIndex和字符長度count的不一樣。在第三句,咱們使str引用爲空,本意是釋放str佔用的空間,可是這個時候,GC是沒法回收這個大的char數組的,由於還在被sub字符串內部引用着,雖然sub只截取這個大數組的一小部分。當str是一個很是大字符串的時候,這種浪費是很是明顯的,甚至會帶來性能問題,解決這個問題能夠是經過如下的方法:
String str = "abcdefghijklmnopqrst"; String sub = str.substring(1, 3) + ""; str = null;
利用的就是字符串的拼接技術,它會建立一個新的字符串,這個新的字符串會使用一個新的內部char數組存儲本身實際須要的字符,這樣父數組的char數組就不會被其餘引用,令str=null,在下一次GC回收的時候會回收整個str佔用的空間。可是這樣書寫很明顯是很差看的,因此在JDK7中,substring 被從新實現了。
在JDK7中改進了substring的實現,它實際是爲截取的子字符串在堆中建立了一個新的char數組用於保存子字符串的字符。
JDK7中String類的substring方法的實現源碼:
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); } public String(char value[], int offset, int count) { if (offset < 0) { throw new StringIndexOutOfBoundsException(offset); } if (count < 0) { throw new StringIndexOutOfBoundsException(count); } // Note: offset or count might be near -1>>>1. if (offset > value.length - count) { throw new StringIndexOutOfBoundsException(offset + count); } this.value = Arrays.copyOfRange(value, offset, offset+count); }
Arrays類的copyOfRange方法,爲子字符串建立了一個新的char數組去存儲子字符串中的字符。這樣子字符串和父字符串也就沒有什麼必然的聯繫了,當父字符串的引用失效的時候,GC就會適時的回收父字符串佔用的內存空間。
void method(){ Vector vector = new Vector(); for (int i = 1; i<100; i++) { Object object = new Object(); vector.add(object); object = null; } //...對vector的操做 //...與vector無關的其餘操做 }
這裏內存泄露指的是在對vector操做完成以後,執行下面與vector無關的代碼時,若是發生了GC操做,這一系列的object是無法被回收的,而此處的內存泄露多是短暫的,容器是方法內的局部變量,形成的內存泄漏影響可能不算很大,由於在整個method()方法執行完成後,那些對象仍是能夠被回收。這裏要解決很簡單,手動賦值爲null便可:
void method(){ Vector vector = new Vector(); for (int i = 1; i<100; i++) { Object object = new Object(); vector.add(object); object = null; } //...對vector的操做 vector = null; // 手動賦值爲null //...與vector無關的其餘操做 }
若是這個容器做爲一個類的成員變量,甚至是一個靜態(static)的成員變量時,就要更加註意內存泄露了。
單例模式,不少時候咱們能夠把它的生命週期與整個程序的生命週期看作差很少的,因此是一個長生命週期的對象。若是這個對象持有其餘對象的引用,也很容易發生內存泄露。