java內存泄露


上一篇提到的是java垃圾回收,今天談談java的內存泄露。

首先談下java的內存管理機制:

在Java程序中,我們通常使用new為對象分配內存,而這些內存空間都在堆(Heap)上。

public class Test {
    public static void main(String args[]){ Object object1 = new Object();//obj1 Object object2 = new Object();//obj2 object2 = object1; } }

在上面的代碼中,創建了兩個對象obj1和obj2,這兩個對象各占用了一部分內存,然而,兩個對象引用變量object1和object2同時指向obj1的那塊內存,而obj2是不可達的,由於java垃圾回收的目標,是清理那些不可達的對象所占內存,釋放對象的根本原則就是對象不會再被使用(也就是沒有引用變量指向該對象所占內存空間),所以obj2是可以被清理的。

下面通過講解幾個例子來說明java的內存泄露:

內存泄露的定義:當某些對象不再被應用程序所使用,但是由於仍然被引用而導致垃圾收集器不能釋放(Remove,移除)他們.

首先,使用一個簡單實現棧的例子類分析: 

public class Stack {

    private  Object[] elements;
    private int size;
    private static final  int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack(){
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    public void push(Object e){
        ensureCapacity();
        elements[size++] = e;
    }
    public Object pop(){
        if (size == 0){
            throw new EmptyStackException();
        }
        return elements[--size];
    }
    private void ensureCapacity(){
        if (elements.length == size){
            elements = Arrays.copyOf(elements,size*2+1);
        }
    }

場景分析:這段程序看上去沒有任何的錯誤,但是,里面隱藏着一個問題,也就是存在一個"內存泄露",隨着垃圾回收器活動的增加以及內存占用的不斷增加,程序性能會逐漸表現出來,極端情況下,會引發OutOfMemoryError導致程序崩潰。

原因分析:程序是模仿棧的功能,如果一個棧先增長,后縮減,那么,從棧彈出的對象所占用的內存空間將不會被垃圾回收器回收,因為棧內部維護着這些對象的過期引用,也就是指永遠也不會被解除的引用,在本段代碼中,凡是在elements數組的"活動部分(指elements中下標小於size的那些元素)"之外的任何引用都是過期的。

解決方法:一旦對象引用已經過期,只需要將之清空即可,否則,如果一個對象引用被無意識的保留下來,那么垃圾回收器不僅不會處理這個對象,而且也不會處理被這個對象所引用的所有其他對象,在本例中,只需將彈出元素的方法pop(),改為以下邏輯

public Object pop(){
        if (size == 0){
            throw new EmptyStackException();
        }
        Object element =  elements[--size];
        elements[size] = null;
        return element;
 }

接下來列出幾種常見而又不易被發現的"內存泄露"

 1.長生命周期的對象持有短生命周期的引用,就很可能會出現內存泄露

public class Test {
    Object object;
    public void test(){
        object = new Object();
        //...其他代碼
    }
}

這個例子中,Test類中的test方法結束后,創建出來的object所占用的內存不會馬上被認為是可以被釋放,嚴格意義上已經導致了垃圾回收。有兩種解決辦法:在test方法結束處,顯示給object ==null,將其打上可被回收的標志;將object作為test方法內部的局部變量。

2.靜態集合類像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變量的生命周期和應用程序一致,所有的對象Object也不能被釋放,因為他們也將一直被Vector等應用着。

static Vector v = new Vector(); 
for (int i = 1; i<100; i++) 

    Object o = new Object(); 
    v.add(o); 
    o = null; 
}

在這個例子中,代碼棧中存在Vector 對象的引用 v 和 Object 對象的引用 o 。在 For 循環,我們不斷的生成新的對象,然后將其添加到 Vector 對象中,之后將 o 引用置空。問題是當 o 引用被置空后,如果發生 GC,我們創建的 Object 對象是否能夠被 GC 回收呢?答案是否定的。因為, GC 在跟蹤代碼棧中的引用時,會發現 v 引用,而繼續往下跟蹤,就會發現 v 引用指向的內存空間中又存在指向 Object 對象的引用。也就是說盡管o 引用已經被置空,但是 Object 對象仍然存在其他的引用,是可以被訪問到的,所以 GC 無法將其釋放掉。如果在此循環之后, Object 對象對程序已經沒有任何作用,那么我們就認為此 Java 程序發生了內存泄漏。

3.當集合里面的對象屬性被修改后,再調用remove()方法時不起作用。

public static void main(String[] args) 
{ 
    Set<Person> set = new HashSet<Person>(); 
    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); 
    } 
}    

4.各種連接,數據庫連接,網絡連接,IO連接等沒有顯示調用close關閉,不被GC回收導致內存泄露,對於Resultset 和Statement 對象可以不進行顯式回收,但Connection 一定要顯式回收,因為Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會立即為NULL;

5.內部類和外部模塊的引用;

6.單例對象在被初始化后將在JVM的整個生命周期中存在(以靜態變量的方式),如果單例對象持有外部對象的引用,那么這個外部對象將不能被jvm正常回收,導致內存泄露;

7.監聽器的使用,在釋放對象的同時沒有相應刪除監聽器的時候也可能導致內存泄露。

內存泄露解決的原則:

1.盡量減少使用靜態變量,類的靜態變量的生命周期和類同步的。  

2.聲明對象引用之前,明確內存對象的有效作用域,盡量減小對象的作用域,將類的成員變量改寫為方法內的局部變量;

3.減少長生命周期的對象持有短生命周期的引用;

4.使用StringBuilder和StringBuffer進行字符串連接,Sting和StringBuilder以及StringBuffer等都可以代表字符串,其中String字符串代表的是不可變的字符串,后兩者表示可變的字符串。如果使用多個String對象進行字符串連接運算,在運行時可能產生大量臨時字符串,這些字符串會保存在內存中從而導致程序性能下降。  

5.對於不需要使用的對象手動設置null值,不管GC何時會開始清理,我們都應及時的將無用的對象標記為可被清理的對象;

6.各種連接(數據庫連接,網絡連接,IO連接)操作,務必顯示調用close關閉。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM