Java中常見的內存泄漏例子

  Java的一個重要特性就是通過垃圾收集器(GC)自動管理內存的回收,而不需要程序員自己來釋放內存。理論上Java中所有不會再被利用的對象所佔用的內存,都可以被GC回收,但是Java也存在內存泄露,但它的表現與C++不同。

JAVA 中的內存泄露

  Java中的內存泄露,廣義並通俗的說,就是:不再會被使用的對象的內存不能被回收,就是內存泄露。
  在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;
    }
}

一些容易發生內存泄露的例子和解決方法

  像上面例子中的情況很容易發生,也是我們最容易忽略並引發內存泄露的情況,解決的原則就是儘量減小對象的作用域(比如android studio中,上面的代碼就會發出警告,並給出的建議是將類的成員變量改寫爲方法內的局部變量)以及手動設置null值。

LinkedList源碼舉例

  至於作用域,需要在我們編寫代碼時多注意;null值的手動設置,我們可以看一下Java容器LinkedList源碼(可參考:Java之LinkedList源碼解讀(JDK 1.8))的刪除指定節點的內部方法:

//刪除指定節點並返回被刪除的元素值
E unlink(Node<E> x) {
    //獲取當前值和前後節點
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    if (prev == null) {
        first = next; //如果前一個節點爲空(如當前節點爲首節點),後一個節點成爲新的首節點
    } else {
        prev.next = next;//如果前一個節點不爲空,那麼他先後指向當前的下一個節點
        x.prev = null;
    }
    if (next == null) {
        last = prev; //如果後一個節點爲空(如當前節點爲尾節點),當前節點前一個成爲新的尾節點
    } else {
        next.prev = prev;//如果後一個節點不爲空,後一個節點向前指向當前的前一個節點
        x.next = null;
    }
    x.item = null;
    size--;
    modCount++;
    return element;
}

  除了修改節點間的關聯關係,我們還要做的就是賦值爲null的操作,不管GC何時會開始清理,我們都應及時的將無用的對象標記爲可被清理的對象。

ArrayList源碼舉例

  我們知道Java容器ArrayList是數組實現的(可參考:Java之ArrayList源碼解讀(JDK 1.8)),如果我們要爲其寫一個pop()(彈出)方法,可能會是這樣:

public E pop(){
    if(size == 0)
        return null;
    else
        return (E) elementData[--size];
}

  寫法很簡潔,但這裏卻會造成內存溢出:elementData[size-1]依然持有E類型對象的引用,並且暫時不能被GC回收。我們可以如下修改:

public E pop(){
    if(size == 0)
        return null;
    else{
        E e = (E) elementData[--size];
        elementData[size] = null;
        return e;
    }
}

  我們寫代碼並不能一味的追求簡潔,首要是保證其正確性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章