Java在何時會出現內存泄漏

在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特色,首先,這些對象是可達的,即在有向圖中,存在通路能夠與其相連;其次,這些對象是無用的,即程序之後不會再使用這些對象。若是對象知足這兩個條件,這些對象就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。java

在C++中,內存泄漏的範圍更大一些。有些對象被分配了內存空間,而後卻不可達,因爲C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,所以程序員不須要考慮這部分的內存泄露。程序員

經過分析,咱們得知,對於C++,程序員須要本身管理邊和頂點,而對於Java程序員只須要管理邊就能夠了(不須要管理頂點的釋放)。經過這種方式,Java提升了編程的效率。算法

 

所以,經過以上分析,咱們知道在Java中也有內存泄漏,但範圍比C++要小一些。由於Java從語言上保證,任何對象都是可達的,全部的不可達對象都由GC管理。數據庫

對於程序員來講,GC基本是透明的,不可見的。雖然,咱們只有幾個函數能夠訪問GC,例如運行GC的函數System.gc(),可是根據Java語言規範定義, 該函數不保證JVM的垃圾收集器必定會執行。由於,不一樣的JVM實現者可能使用不一樣的算法管理GC。一般,GC的線程的優先級別較低。JVM調用GC的策略也有不少種,有的是內存使用到達必定程度時,GC纔開始工做,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但一般來講,咱們不須要關心這些。除非在一些特定的場合,GC的執行影響應用程序的性能,例如對於基於Web的實時系統,如網絡遊戲等,用戶不但願GC忽然中斷應用程序執行而進行垃圾回收,那麼咱們須要調整GC的參數,讓GC可以經過平緩的方式釋放內存,例如將垃圾回收分解爲一系列的小步驟執行,Sun提供的HotSpot JVM就支持這一特性。編程

一樣給出一個Java內存泄漏的典型例子,網絡

Vector v = new Vector(10);socket

for (int i = 1; i < 100; i++) {函數

    Object o = new Object();性能

    v.add(o);this

    o = null;

}

在這個例子中,咱們循環申請Object對象,並將所申請的對象放入一個 Vector 中,若是咱們僅僅釋放引用自己,那麼 Vector 仍然引用該對象,因此這個對象對 GC 來講是不可回收的。所以,若是對象加入到Vector 後,還必須從 Vector 中刪除,最簡單的方法就是將 Vector 對象設置爲 null。

詳細Java中的內存泄漏

1.Java內存回收機制

不論哪一種語言的內存分配方式,都須要返回所分配內存的真實地址,也就是返回一個指針到內存塊的首地址。Java中對象是採用new或者反射的方法建立的,這些對象的建立都是在堆(Heap)中分配的,全部對象的回收都是由Java虛擬機經過垃圾回收機制完成的。GC爲了可以正確釋放對象,會監控每一個對象的運行情況,對他們的申請、引用、被引用、賦值等情況進行監控,Java會使用有向圖的方法進行管理內存,實時監控對象是否能夠達到,若是不可到達,則就將其回收,這樣也能夠消除引用循環的問題。在Java語言中,判斷一個內存空間是否符合垃圾收集標準有兩個:一個是給對象賦予了空值null,如下再沒有調用過,另外一個是給對象賦予了新值,這樣從新分配了內存空間。

2.Java內存泄漏引發的緣由

內存泄漏是指無用對象(再也不使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而形成內存空間的浪費稱爲內存泄漏。內存泄露有時不嚴重且不易察覺,這樣開發者就不知道存在內存泄露,但有時也會很嚴重,會提示你Out of memory。

Java內存泄漏的根本緣由是什麼呢?長生命週期的對象持有短生命週期對象的引用就極可能發生內存泄漏,儘管短生命週期對象已經再也不須要,可是由於長生命週期持有它的引用而致使不能被回收,這就是Java中內存泄漏的發生場景。具體主要有以下幾大類:

一、靜態集合類引發內存泄漏:

像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,他們所引用的全部的對象Object也不能被釋放,由於他們也將一直被Vector等引用着。

例如

Static Vector v = new Vector(10);

for (int i = 1; i<100; i++)

{

    Object o = new Object();

    v.add(o);

    o = null;

}

在這個例子中,循環申請Object對象,並將所申請的對象放入一個Vector 中,若是僅僅釋放引用自己(o=null),那麼Vector 仍然引用該對象,因此這個對象對GC 來講是不可回收的。所以,若是對象加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector對象設置爲null。

二、當集合裏面的對象屬性被修改後,再調用remove()方法時不起做用。

例如:

public static void main(String[] args)

{

    Set set = new HashSet();

    Person p1 = new Person("唐僧","pwd1",25);

    Person p2 = new Person("孫悟空","pwd2",26);

    Person p3 = new Person("豬八戒","pwd3",27);

    set.add(p1);

    set.add(p2);

    set.add(p3);

    System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:3 個元素!

    p3.setAge(2); //修改p3的年齡,此時p3元素對應的hashcode值發生改變

    set.remove(p3); //此時remove不掉,形成內存泄漏

    set.add(p3); //從新添加,竟然添加成功

    System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:4 個元素!

    for (Person person : set)

    {

        System.out.println(person);

    }

}

三、監聽器

在java編程中,咱們都須要和監聽器打交道,一般一個應用當中會用到不少監聽器,咱們會調用一個控件的諸如addXXXListener()等方法來增長監聽器,但每每在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增長了內存泄漏的機會。

四、各類鏈接

好比數據庫鏈接(dataSourse.getConnection()),網絡鏈接(socket)和io鏈接,除非其顯式的調用了其close()方法將其鏈接關閉,不然是不會自動被GC 回收的。對於Resultset 和Statement 對象能夠不進行顯式回收,但Connection 必定要顯式回收,由於Connection 在任什麼時候候都沒法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會當即爲NULL。可是若是使用鏈接池,狀況就不同了,除了要顯式地關閉鏈接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另一個也會關閉),不然就會形成大量的Statement 對象沒法釋放,從而引發內存泄漏。這種狀況下通常都會在try裏面去的鏈接,在finally裏面釋放鏈接。

五、內部類和外部模塊的引用

內部類的引用是比較容易遺忘的一種,並且一旦沒釋放可能致使一系列的後繼類對象沒有釋放。此外程序員還要當心外部模塊不經意的引用,例如程序員A負責A 模塊,調用了B 模塊的一個方法如:

public void registerMsg(Object b);

這種調用就要很是當心了,傳入了一個對象,極可能模塊B就保持了對該對象的引用,這時候就須要注意模塊B 是否提供相應的操做去除引用。

六、單例模式

不正確使用單例模式是引發內存泄漏的一個常見問題,單例對象在初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部的引用,那麼這個對象將不能被JVM正常回收,致使內存泄漏,考慮下面的例子:

class A{

    public A(){

    B.getInstance().setA(this);

    }

....

    }

//B類採用單例模式

class B{

    private A a;

    private static B instance=new B();

    public B(){}

    public static B getInstance(){

        return instance;

    }

    public void setA(A a){

        this.a=a;

    }

    //getter...

}

顯然B採用singleton模式,它持有一個A對象的引用,而這個A類的對象將不能被回收。想象下若是A是個比較複雜的對象或者集合類型會發生什麼狀況。

相關文章
相關標籤/搜索