內存泄露是指一個再也不被程序使用的對象或變量還在內存中佔有存儲空間。在C/C++語言中,內存的分配與釋放是由開發人員來負責的,若是開發人員忘記釋放已分配的內存就會形成內存泄露。而在Java語言中引進了垃圾回收機制,由垃圾回收器負責回收再也不使用的對象,既然有垃圾回收器來負責回收垃圾,那麼是否還會存在內存泄露的問題呢?java
其實,在Java語言中,判斷一個內存空間是否符合垃圾回收的標準有兩個:第一,給對象賦予了空值null,之後再沒有被使用過;第二,給對象賦予了新值,從新分配了內存空間。通常來說,內存泄露主要有兩種狀況:一是在堆中申請的空間沒有被釋放;二是對象已再也不被使用,但仍然還在內存中保留着。垃圾回收機制的引入能夠有效地解決第一種狀況;而對於第二種狀況,垃圾回收機制則沒法保證再也不使用的對象會被釋放。所以,Java語言中的內存泄露主要指的是第二種狀況。數據庫
下面經過一個示例來介紹Java語言中的內存泄露:網絡
Vector v = new Vector(); for(int i=1;i<10;i++) { Object o = new Object(); v.add(o); }
在上述示例的循環中,不斷建立新的對象加到Vector對象中,當退出循環後,o的做用域將會結束,可是因爲v在使用這些對象,所以垃圾回收器沒法將其回收,此時就造化了內存泄露。只有將這些對象從Vector中刪除才能釋放建立的這些對象。this
在Java語言中,容易引發內存泄露的緣由不少,主要有一下幾個方面的內容:code
1)靜態集合類,例如HashMap和Vector。若是這些容器爲靜態的,因爲他們的生命週期與程序一致,那麼容器中的對象在程序結束以前將不能被釋放,從而形成內存泄露,如上例所示。對象
2)各類鏈接,例如數據庫鏈接、網絡鏈接以及IO鏈接等。在對數據庫進行操做的過程當中,首先須要創建與數據庫的鏈接,當不在使用時,須要調用close方法來釋放與數據庫的鏈接。只有鏈接被關閉後垃圾回收器纔會回收對應的對象。不然,若是在訪問數據庫的過程當中,對Connection、Statement或ResultSet不顯示地關閉,將會形成大量的對象沒法被回收,從而引發內存泄露。生命週期
3)監聽器。在Java語言中,每每會使用到監聽器。一般一個應用中會應用到多個監聽器,但在釋放對象的同時每每沒有相應的刪除監聽器,這也可能致使內存泄露。內存
4)變量不合理的做用域。通常而言,若是一個變量的做用範圍大於其使用範圍,頗有可能會形成內存泄露,另外一方面若是沒有及時地把對象設置爲null,頗有可能會致使內存泄露的發生,示例以下:作用域
class Server{ private String msg; public void receiveMsg() { readFromNet(); saveDB(); } }
在上述的僞代碼中,經過readFromNet()方法接受的消息保存在變量msg中,而後調用saveDB()方法把msg的內容保存到數據庫中,此時msg已經沒有用了,可是因爲msg的生命週期與對象的生命週期相同,此時msg還不能被回收,一次形成了內存泄露。對於這種問題,有以下兩種解決方法:第一種方法,因爲msg的做用範圍只在receiveMsg()方法內,一次能夠把msg定義爲這個方法的局部變量,當方法結束後,msg的生命週期就會結束,此時垃圾回收器就能夠回收msg的內容了;第二種方法,在使用完msg後就把msg設置爲null,這樣垃圾回收器也會自動回收msg內容所佔的內存空間。開發
5)單例模式可能會形成內存泄露。單例模式的實現方法有不少種,下例中所使用的單例模式就可能會形成內存泄露:
class BigClass{ //class body } class Singleton{ private BigClass bc; private static Singleton instance = new Singleton(new BigClass()); private Singleton(BigClass bc) { this.bc = bc; } public Singleton getInstance() { return instance; } }
在上述實現的單例模式中,Singleton存在一個對對象BigClass的引用,因爲單例對象以靜態變量的方式存儲,所以它在JVM的整個生命週期中都存在,同時因爲它有一個對對象BigClass的引用,這樣會致使BigClass類的對象不可以被回收。