內存泄露是指一個不再被程序使用的對象或變量還在內存中占有存儲空間。
在C/C++中,內存分配與釋放是由開發人員來負責的,如果開發者忘記釋放已經分配的內存就會造成內存泄露。但是在java中,內存的回收是通過垃圾回收器(GC)來實現的,那么在java中出現內存泄露問題是怎么發生的呢?
通過內存泄露的定義,我們不難得出內存泄露的兩種情況:
- 在堆中申請的空間沒有被釋放;
2.對象已不在被使用,但是還在內存中保留著。
首先,判斷一個內存空間是否符合垃圾回收的標準:一、對象的值是空值null,以后在沒有被使用過。二、給對象賦予一個新值,重新分配了內存空間。通過對GC回收標準的分析,我們可以知道,在java中出現內存泄露的情況一般都是第二種情況。如果這里還有疑問的么可以參照我前面的博客GC簡介。下面我們對其進行一個全面的分析。
實現給出一個實例來了解Java中內存泄露:
Vector v = new Vector();
for(int i=0;i<10;i++){
Object o = new Object();
v.add(o);
}
看起來好像沒什么問題,好像我們平時也會這么寫對吧。但是實際上,在上面例子的循環中,不斷常見新的對象加到Veactor對象中,當退出循環后,o的作用域將會結束,但是由于v在使用這么對象,因此垃圾回收器無法對其回收,此時就造成了內存泄露。只有將這些對象從Vector中刪除才能釋放創建的對象。
在java中,能引用內存泄露的原因很多,主要體現在以下的幾個方面:
1) 靜態集合類,例如HashMap和Vector。如果這些容器為靜態的。由于他們的生命周期與程序一致,那么容器中對祥在程序結束之前將不能被釋放,從而造成內存泄露。如上例所示。
2)各種連接沒有及時斷開,例如數據庫鏈接、網絡連接以及IO連接等。在對數據庫進行操作時,首先需要建立與數據庫的連接,當不在使用時,需調用close方法來釋放與數據庫的連接。只有連接被關閉后,垃圾回收器才能回收相對的對象,否則,如果在訪問數據庫的過程中,對Connection、Statement不顯示的關閉,將會造成大量的對象無法被回收。
3) 監聽器。在Java語言中,往往會使用到監聽器。通常一個應用會用到多個監聽器,但在釋放對象的同時往往沒有相對應的刪除監聽器,這也可能導致內存泄露。
4)變量不合理的作用域。例如下面的實例:
class Servce{
private String msg;
public void recieveMsg(){
//從網絡接受數據保存到msg中
//把msg保存到數據庫中
}
}
在上述代碼中,在revieveMsg()方法將接受的消息保存在變量msg中,并保存在數據中中。保存到數據中之后,msg已經沒有用了, 但是由于Msg的生命周期與對象的生命周期一樣長,此時msg還不能被回收,因此會造成內存泄露。對于這個問題,有兩種解決方案,一種方式時將Msg定義在recieveMsg()方法下,另一種方式時在Msg使用完之后,將msg設置為null。這樣垃圾回收器也會自動回收msg內容所占的內存空間了。
5)不適當的單利模式。
class BigClass{
//***
}
class Sington{
private BigClass big;
private Sington(BigClass big){this/big = big;}
private static Singleton instale = new Sington(new BigClass());
public Singleton getInstance(){
return instance;
}
}
在上述的單利模式中,sington存在一個對對象BigClass的引用,由于單利對象以靜態變量的方式存儲,因此他在JVM的整個生命周期中都存在,同時由于他有一個對對象BigClass的引用,這樣會導致BigClass類的對象不能夠被回收。