Java內存泄露的理解與解決

轉載請註明出處:http://www.blogjava.net/zh-weir/archive/2011/02/23/345007.htmlhtml

Java內存管理機制

C++語言中,若是須要動態分配一塊內存,程序員須要負責這塊內存的整個生命週期。從申請分配、到使用、再到最後的釋放。這樣的過程很是靈活,可是卻十分繁瑣,程序員很容易因爲疏忽而忘記釋放內存,從而致使內存的泄露。Java語言對內存管理作了本身的優化,這就是垃圾回收機制。Java的幾乎全部內存對象都是在堆內存上分配(基本數據類型除外),而後由GCgarbage collection)負責自動回收再也不使用的內存。java

    上面是Java內存管理機制的基本狀況。可是若是僅僅理解到這裏,咱們在實際的項目開發中仍然會遇到內存泄漏的問題。也許有人表示懷疑,既然Java的垃圾回收機制可以自動的回收內存,怎麼還會出現內存泄漏的狀況呢?這個問題,咱們須要知道GC在何時回收內存對象,什麼樣的內存對象會被GC認爲是「再也不使用」的。android

    Java中對內存對象的訪問,使用的是引用的方式。在Java代碼中咱們維護一個內存對象的引用變量,經過這個引用變量的值,咱們能夠訪問到對應的內存地址中的內存對象空間。在Java程序中,這個引用變量自己既能夠存放堆內存中,又能夠放在代碼棧的內存中(與基本數據類型相同)。GC線程會從代碼棧中的引用變量開始跟蹤,從而斷定哪些內存是正在使用的。若是GC線程經過這種方式,沒法跟蹤到某一塊堆內存,那麼GC就認爲這塊內存將再也不使用了(由於代碼中已經沒法訪問這塊內存了)。

程序員


 

    經過這種有向圖的內存管理方式,當一個內存對象失去了全部的引用以後,GC就能夠將其回收。反過來講,若是這個對象還存在引用,那麼它將不會被GC回收,哪怕是Java虛擬機拋出OutOfMemoryError

算法

Java內存泄露

    通常來講內存泄漏有兩種狀況。一種狀況如在C/C++語言中的,在堆中的分配的內存,在沒有將其釋放掉的時候,就將全部能訪問這塊內存的方式都刪掉(如指針從新賦值);另外一種狀況則是在內存對象明明已經不須要的時候,還仍然保留着這塊內存和它的訪問方式(引用)。第一種狀況,在Java中已經因爲垃圾回收機制的引入,獲得了很好的解決。因此,Java中的內存泄漏,主要指的是第二種狀況。緩存

    可能光說概念太抽象了,你們能夠看一下這樣的例子:

數據結構

1 Vector v=new Vector(10);
2 for (int i=1;i<100; i++){
3 Object o=new Object();
4 v.add(o);
5 o=null;
6 }app

   
    在這個例子中,代碼棧中存在Vector對象的引用vObject對象的引用o。在For循環中,咱們不斷的生成新的對象,而後將其添加到Vector對象中,以後將o引用置空。問題是當o引用被置空後,若是發生GC,咱們建立的Object對象是否可以被GC回收呢?答案是否認的。由於,GC在跟蹤代碼棧中的引用時,會發現v引用,而繼續往下跟蹤,就會發現v引用指向的內存空間中又存在指向Object對象的引用。也就是說盡管o引用已經被置空,可是Object對象仍然存在其餘的引用,是能夠被訪問到的,因此GC沒法將其釋放掉。若是在此循環以後,Object對象對程序已經沒有任何做用,那麼咱們就認爲此Java程序發生了內存泄漏。
ide

    儘管對於C/C++中的內存泄露狀況來講,Java內存泄露致使的破壞性小,除了少數狀況會出現程序崩潰的狀況外,大多數狀況下程序仍然能正常運行。可是,在移動設備對於內存和CPU都有較嚴格的限制的狀況下,Java的內存溢出會致使程序效率低下、佔用大量不須要的內存等問題。這將致使整個機器性能變差,嚴重的也會引發拋出OutOfMemoryError,致使程序崩潰。

函數

通常狀況下內存泄漏的避免

    在不涉及複雜數據結構的通常狀況下,Java的內存泄露表現爲一個內存對象的生命週期超出了程序須要它的時間長度。咱們有時也將其稱爲「對象遊離」。

例如:

 1 public class FileSearch{
 2 
 3     private byte[] content;
 4     private File mFile;
 5     
 6     public FileSearch(File file){
 7         mFile = file;
 8     }
 9 
10     public boolean hasString(String str){
11         int size = getFileSize(mFile);
12         content = new byte[size];
13         loadFile(mFile, content);
14         
15         String s = new String(content);
16         return s.contains(str);
17     }
18 }


    在這段代碼中,FileSearch類中有一個函數
hasString,用來判斷文檔中是否含有指定的字符串。流程是先將mFile加載到內存中,而後進行判斷。可是,這裏的問題是,將content聲明爲了實例變量,而不是本地變量。因而,在此函數返回以後,內存中仍然存在整個文件的數據。而很明顯,這些數據咱們後續是再也不須要的,這就形成了內存的無端浪費。

    要避免這種狀況下的內存泄露,要求咱們以C/C++的內存管理思惟來管理本身分配的內存。第一,是在聲明對象引用以前,明確內存對象的有效做用域。在一個函數內有效的內存對象,應該聲明爲local變量,與類實例生命週期相同的要聲明爲實例變量……以此類推。第二,在內存對象再也不須要時,記得手動將其引用置空。

複雜數據結構中的內存泄露問題

    在實際的項目中,咱們常常用到一些較爲複雜的數據結構用於緩存程序運行過程當中須要的數據信息。有時,因爲數據結構過於複雜,或者咱們存在一些特殊的需求(例如,在內存容許的狀況下,儘量多的緩存信息來提升程序的運行速度等狀況),咱們很難對數據結構中數據的生命週期做出明確的界定。這個時候,咱們可使用Java中一種特殊的機制來達到防止內存泄露的目的。

    以前咱們介紹過,JavaGC機制是創建在跟蹤內存的引用機制上的。而在此以前,咱們所使用的引用都只是定義一個「Object o;」這樣形式的。事實上,這只是Java引用機制中的一種默認狀況,除此以外,還有其餘的一些引用方式。經過使用這些特殊的引用機制,配合GC機制,就能夠達到一些咱們須要的效果。

Java中的幾種引用方式

    Java中有幾種不一樣的引用方式,它們分別是:強引用、軟引用、弱引用和虛引用。下面,咱們首先詳細地瞭解下這幾種引用方式的意義。

    
      強引用

在此以前咱們介紹的內容中所使用的引用都是強引用,這是使用最廣泛的引用。若是一個對象具備強引用,那就相似於必不可少的生活用品,垃圾回收器毫不會回收它。當內存空 間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足問題。

軟引用(SoftReference

SoftReference 類的一個典型用途就是用於內存敏感的高速緩存。SoftReference 的原理是:在保持對對象的引用時保證在 JVM 報告內存不足狀況以前將清除全部的軟引用。關鍵之處在於,垃圾收集器在運行時可能會(也可能不會)釋放軟可及對象。對象是否被釋放取決於垃圾收集器的算法 以及垃圾收集器運行時可用的內存數量。

弱引用(WeakReference

WeakReference 類的一個典型用途就是規範化映射(canonicalized mapping)。另外,對於那些生存期相對較長並且從新建立的開銷也不高的對象來講,弱引用也比較有用。關鍵之處在於,垃圾收集器運行時若是碰到了弱可及對象,將釋放 WeakReference 引用的對象。然而,請注意,垃圾收集器可能要運行屢次才能找到並釋放弱可及對象。

虛引用(PhantomReference

PhantomReference 類只能用於跟蹤對被引用對象即將進行的收集。一樣,它還能用於執行 pre-mortem 清除操做。PhantomReference 必須與 ReferenceQueue 類一塊兒使用。須要 ReferenceQueue 是由於它可以充當通知機制。當垃圾收集器肯定了某個對象是虛可及對象時,PhantomReference 對象就被放在它的 ReferenceQueue 上。將 PhantomReference 對象放在 ReferenceQueue 上也就是一個通知,代表 PhantomReference 對象引用的對象已經結束,可供收集了。這使您可以恰好在對象佔用的內存被回收以前採起行動。ReferenceReferenceQueue的配合使用。

GCReferenceReferenceQueue的交互

A、 GC沒法刪除存在強引用的對象的內存。

B、 GC發現一個只有軟引用的對象內存,那麼:

① SoftReference對象的referent 域被設置爲null,從而使該對象再也不引用heap對象。

② SoftReference引用過的heap對象被聲明爲finalizable

③ 當 heap 對象的 finalize() 方法被運行並且該對象佔用的內存被釋放,SoftReference 對象就被添加到它的 ReferenceQueue(若是後者存在的話)。

C、 GC發現一個只有弱引用的對象內存,那麼:

① WeakReference對象的referent域被設置爲null,從而使該對象再也不引用heap對象。

② WeakReference引用過的heap對象被聲明爲finalizable

③ heap對象的finalize()方法被運行並且該對象佔用的內存被釋放時,WeakReference對象就被添加到它的ReferenceQueue(若是後者存在的話)。

D、 GC發現一個只有虛引用的對象內存,那麼:

① PhantomReference引用過的heap對象被聲明爲finalizable

② PhantomReference在堆對象被釋放以前就被添加到它的ReferenceQueue

值得注意的地方有如下幾點:

1GC在通常狀況下不會發現軟引用的內存對象,只有在內存明顯不足的時候纔會發現並釋放軟引用對象的內存。

2GC對弱引用的發現和釋放也不是當即的,有時須要重複幾回GC,纔會發現並釋放弱引用的內存對象。
3、軟引用和弱引用在添加到ReferenceQueue的時候,其指向真實內存的引用已經被置爲空了,相關的內存也已經被釋放掉了。而虛引用在添加到ReferenceQueue的時候,內存尚未釋放,仍然能夠對其進行訪問。

    代碼示例

經過以上的介紹,相信您對Java的引用機制以及幾種引用方式的異同已經有了必定了解。光是概念,可能過於抽象,下面咱們經過一個例子來演示如何在代碼中使用Reference機制。

1     String str = new String("hello"); //
2     ReferenceQueue<String> rq = new ReferenceQueue<String>(); //
3     WeakReference<String> wf = new WeakReference<String>(str, rq); //
4     str=null//④取消"hello"對象的強引用
5     String str1=wf.get(); //⑤假如"hello"對象沒有被回收,str1引用"hello"對象
6     //假如"hello"對象沒有被回收,rq.poll()返回null
7     Reference<? extends String> ref=rq.poll(); //


在以上代碼中,注意⑤⑥兩處地方。假如「hello」對象沒有被回收wf.get()將返回「hello」字符串對象,rq.poll()返回null;而加入「hello」對象已經被回收了,那麼wf.get()返回nullrq.poll()返回Reference對象,可是此Reference對象中已經沒有str對象的引用了(PhantomReference則與WeakReferenceSoftReference不一樣)

    引用機制與複雜數據結構的聯合應用

    瞭解了GC機制、引用機制,並配合上ReferenceQueue,咱們就能夠實現一些防止內存溢出的複雜數據類型。

例如,SoftReference具備構建Cache系統的特質,所以咱們能夠結合哈希表實現一個簡單的緩存系統。這樣既能保證可以儘量多的緩存信息,又能夠保證Java虛擬機不會由於內存泄露而拋出OutOfMemoryError。這種緩存機制特別適合於內存對象生命週期長,且生成內存對象的耗時比較長的狀況,例如緩存列表封面圖片等。對於一些生命週期較長,可是生成內存對象開銷不大的狀況,使用WeakReference可以達到更好的內存管理的效果。

SoftHashmap的源碼一份,相信看過以後,你們會對Reference機制的應用有更深刻的理解。

  1package com.***.widget;
  2
  3//: SoftHashMap.java 
  4import java.util.*; 
  5import java.lang.ref.*; 
  6
  7import android.util.Log;
  8
  9public class SoftHashMap extends AbstractMap 
 10  /** The internal HashMap that will hold the SoftReference. */ 
 11  private final Map hash = new HashMap(); 
 12  /** The number of "hard" references to hold internally. */ 
 13  private final int HARD_SIZE; 
 14  /** The FIFO list of hard references, order of last access. */ 
 15  private final LinkedList hardCache = new LinkedList(); 
 16  /** Reference queue for cleared SoftReference objects. */ 
 17  private ReferenceQueue queue = new ReferenceQueue(); 
 18
 19  //Strong Reference number
 20  public SoftHashMap() this(100); } 
 21  public SoftHashMap(int hardSize) { HARD_SIZE = hardSize; } 
 22  
 23
 24  public Object get(Object key) 
 25    Object result = null
 26    // We get the SoftReference represented by that key 
 27    SoftReference soft_ref = (SoftReference)hash.get(key); 
 28    if (soft_ref != null
 29      // From the SoftReference we get the value, which can be 
 30      // null if it was not in the map, or it was removed in 
 31      // the processQueue() method defined below 
 32      result = soft_ref.get(); 
 33      if (result == null
 34        // If the value has been garbage collected, remove the 
 35        // entry from the HashMap. 
 36        hash.remove(key); 
 37      }
 else 
 38        // We now add this object to the beginning of the hard 
 39        // reference queue.  One reference can occur more than 
 40        // once, because lookups of the FIFO queue are slow, so 
 41        // we don't want to search through it each time to remove 
 42        // duplicates. 
 43          //keep recent use object in memory
 44        hardCache.addFirst(result); 
 45        if (hardCache.size() > HARD_SIZE) 
 46          // Remove the last entry if list longer than HARD_SIZE 
 47          hardCache.removeLast(); 
 48        }
 
 49      }
 
 50    }
 
 51    return result; 
 52  }
 
 53
 54  /** We define our own subclass of SoftReference which contains 
 55   not only the value but also the key to make it easier to find 
 56   the entry in the HashMap after it's been garbage collected. */
 
 57  private static class SoftValue extends SoftReference 
 58    private final Object key; // always make data member final 
 59    /** Did you know that an outer class can access private data 
 60     members and methods of an inner class?  I didn't know that! 
 61     I thought it was only the inner class who could access the 
 62     outer class's private information.  An outer class can also 
 63     access private members of an inner class inside its inner 
 64     class. */
 
 65    private SoftValue(Object k, Object key, ReferenceQueue q) 
 66      super(k, q); 
 67      this.key = key; 
 68    }
 
 69  }
 
 70
 71  /** Here we go through the ReferenceQueue and remove garbage 
 72   collected SoftValue objects from the HashMap by looking them 
 73   up using the SoftValue.key data member. */
 
 74  public void processQueue() 
 75    SoftValue sv; 
 76    while ((sv = (SoftValue)queue.poll()) != null
 77        if(sv.get()== null){
 78            Log.e("processQueue", "null");
 79        }
else{
 80            Log.e("processQueue", "Not null");
 81        }

 82      hash.remove(sv.key); // we can access private data!
 83      Log.e("SoftHashMap", "release " + sv.key);
 84    }
 
 85  }
 
 86  /** Here we put the key, value pair into the HashMap using 
 87   a SoftValue object. */
 
 88  public Object put(Object key, Object value) 
 89    processQueue(); // throw out garbage collected values first 
 90    Log.e("SoftHashMap", "put into " + key);
 91    return hash.put(key, new SoftValue(value, key, queue)); 
 92  }
 
 93  public Object remove(Object key) 
 94    processQueue(); // throw out garbage collected values first 
 95    return hash.remove(key); 
 96  }
 
 97  public void clear() 
 98    hardCache.clear(); 
 99    processQueue(); // throw out garbage collected values 
100    hash.clear(); 
101  }
 
102  public int size() 
103    processQueue(); // throw out garbage collected values first 
104    return hash.size(); 
105  }
 
106  public Set entrySet() 
107    // no, no, you may NOT do that!!! GRRR 
108    throw new UnsupportedOperationException(); 
109  }
 
110}
 
111
112
113

相關文章
相關標籤/搜索