Java內存泄露分析

Java內存管理機制 java

     在C++ 語言中,若是須要動態分配一塊內存,程序員須要負責這塊內存的整個生命週期。從申請分配、到使用、再到最後的釋放。這樣的過程很是靈活,可是卻十分繁瑣,程序員很容易因爲疏忽而忘記釋放內存,從而致使內存的泄露。 Java 語言對內存管理作了本身的優化,這就是垃圾回收機制。 Java 的幾乎全部內存對象都是在堆內存上分配(基本數據類型除外),而後由 GC ( garbage collection)負責自動回收再也不使用的內存。 
     上面是Java 內存管理機制的基本狀況。可是若是僅僅理解到這裏,咱們在實際的項目開發中仍然會遇到內存泄漏的問題。也許有人表示懷疑,既然 Java 的垃圾回收機制可以自動的回收內存,怎麼還會出現內存泄漏的狀況呢?這個問題,咱們須要知道 GC 在何時回收內存對象,什麼樣的內存對象會被 GC 認爲是「再也不使用」的。 
      Java中對內存對象的訪問,使用的是引用的方式。在 Java 代碼中咱們維護一個內存對象的引用變量,經過這個引用變量的值,咱們能夠訪問到對應的內存地址中的內存對象空間。在 Java 程序中,這個引用變量自己既能夠存放堆內存中,又能夠放在代碼棧的內存中(與基本數據類型相同)。 GC 線程會從代碼棧中的引用變量開始跟蹤,從而斷定哪些內存是正在使用的。若是 GC 線程經過這種方式,沒法跟蹤到某一塊堆內存,那麼 GC 就認爲這塊內存將再也不使用了(由於代碼中已經沒法訪問這塊內存了)。 

       經過這種有向圖的內存管理方式,當一個內存對象失去了全部的引用以後,GC 就能夠將其回收。反過來講,若是這個對象還存在引用,那麼它將不會被 GC 回收,哪怕是 Java 虛擬機拋出 OutOfMemoryError 。 
Java內存泄露
      通常來講內存泄漏有兩種狀況。一種狀況如在C/C++ 語言中的,在堆中的分配的內存,在沒有將其釋放掉的時候,就將全部能訪問這塊內存的方式都刪掉(如指針從新賦值);另外一種狀況則是在內存對象明明已經不須要的時候,還仍然保留着這塊內存和它的訪問方式(引用)。第一種狀況,在 Java 中已經因爲垃圾回收機制的引入,獲得了很好的解決。因此, Java 中的內存泄漏,主要指的是第二種狀況。 
      可能光說概念太抽象了,你們能夠看一下這樣的例子: android

Vector v = new Vector( 10 ); 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 程序發生了內存泄漏。 
儘管對於C/C++ 中的內存泄露狀況來講, Java 內存泄露致使的破壞性小,除了少數狀況會出現程序崩潰的狀況外,大多數狀況下程序仍然能正常運行。可是,在移動設備對於內存和 CPU 都有較嚴格的限制的狀況下, Java 的內存溢出會致使程序效率低下、佔用大量不須要的內存等問題。這將致使整個機器性能變差,嚴重的也會引發拋出 OutOfMemoryError ,致使程序崩潰。 
通常狀況下內存泄漏的避免 
      在不涉及複雜數據結構的通常狀況下,Java 的內存泄露表現爲一個內存對象的生命週期超出了程序須要它的時間長度。咱們有時也將其稱爲「對象遊離」。 
例如: 程序員

public class FileSearch{ private byte [] content; private File mFile; public FileSearch(File file){ mFile = file; } public boolean hasString(String str){ int size = getFileSize(mFile); content = new byte [size]; loadFile(mFile, content); String s = new String(content); return s.contains(str); } }

在這段代碼中,FileSearch 類中有一個函數 hasString ,用來判斷文檔中是否含有指定的字符串。流程是先將mFile 加載到內存中,而後進行判斷。可是,這裏的問題是,將 content 聲明爲了實例變量,而不是本地變量。因而,在此函數返回以後,內存中仍然存在整個文件的數據。而很明顯,這些數據咱們後續是再也不須要的,這就形成了內存的無端浪費。 
要避免這種狀況下的內存泄露,要求咱們以C/C++ 的內存管理思惟來管理本身分配的內存。第一,是在聲明對象引用以前,明確內存對象的有效做用域。在一個函數內有效的內存對象,應該聲明爲 local 變量,與類實例生命週期相同的要聲明爲實例變量……以此類推。第二,在內存對象再也不須要時,記得手動將其引用置空。 
複雜數據結構中的內存泄露問題 
在實際的項目中,咱們常常用到一些較爲複雜的數據結構用於緩存程序運行過程當中須要的數據信息。有時,因爲數據結構過於複雜,或者咱們存在一些特殊的需求(例如,在內存容許的狀況下,儘量多的緩存信息來提升程序的運行速度等狀況),咱們很難對數據結構中數據的生命週期做出明確的界定。這個時候,咱們可使用Java中一種特殊的機制來達到防止內存泄露的目的。 
以前咱們介紹過,Java 的 GC 機制是創建在跟蹤內存的引用機制上的。而在此以前,咱們所使用的引用都只是定義一個「 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  對象引用的對象已經結束,可供收集了。這使您可以恰好在對象佔用的內存被回收以前採起行動。 Reference與 ReferenceQueue 的配合使用。 
GC、 Reference 與 ReferenceQueue 的交互 
A、  GC沒法刪除存在強引用的對象的內存。 
B、  GC發現一個只有軟引用的對象內存,那麼: 算法

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

②  SoftReference引用過的 heap 對象被聲明爲 finalizable 。 數據結構

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


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

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

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

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


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

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

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

 

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


一、 GC 在通常狀況下不會發現軟引用的內存對象,只有在內存明顯不足的時候纔會發現並釋放軟引用對象的內存。 
二、 GC 對弱引用的發現和釋放也不是當即的,有時須要重複幾回 GC ,纔會發現並釋放弱引用的內存對象。 
三、軟引用和弱引用在添加到 ReferenceQueue 的時候,其指向真實內存的引用已經被置爲空了,相關的內存也已經被釋放掉了。而虛引用在添加到 ReferenceQueue 的時候,內存尚未釋放,仍然能夠對其進行訪問。 
代碼示例 
經過以上的介紹,相信您對Java 的引用機制以及幾種引用方式的異同已經有了必定了解。光是概念,可能過於抽象,下面咱們經過一個例子來演示如何在代碼中使用 Reference 機制。

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

在以上代碼中,注意⑤⑥兩處地方。假如「hello 」對象沒有被回收 wf.get() 將返回「 hello 」字符串對象, rq.poll() 返回 null ;而加入「 hello 」對象已經被回收了,那麼 wf.get() 返回 null , rq.poll() 返回 Reference 對象,可是此 Reference 對象中已經沒有 str 對象的引用了 ( PhantomReference 則與WeakReference 、 SoftReference 不一樣 )。 
引用機制與複雜數據結構的聯合應用 
瞭解了GC 機制、引用機制,並配合上 ReferenceQueue ,咱們就能夠實現一些防止內存溢出的複雜數據類型。 
例如,SoftReference 具備構建 Cache 系統的特質,所以咱們能夠結合哈希表實現一個簡單的緩存系統。這樣既能保證可以儘量多的緩存信息,又能夠保證 Java 虛擬機不會由於內存泄露而拋出 OutOfMemoryError 。這種緩存機制特別適合於內存對象生命週期長,且生成內存對象的耗時比較長的狀況,例如緩存列表封面圖片等。對於一些生命週期較長,可是生成內存對象開銷不大的狀況,使用WeakReference 可以達到更好的內存管理的效果。 
附SoftHashmap 的源碼一份,相信看過以後,你們會對 Reference 機制的應用有更深刻的理解。

package  com. *** .widget;  
    // : SoftHashMap.java  
    import  java.util. * ;  
    import  java.lang.ref. * ;  
    import  android.util.Log;  
    
    public   class  SoftHashMap  extends  AbstractMap  {  
      /**  The internal HashMap that will hold the SoftReference.  */    
      private   final  Map hash  =   new  HashMap();  
      /**  The number of "hard" references to hold internally.  */    
      private   final   int  HARD_SIZE;  
      /**  The FIFO list of hard references, order of last access.  */    
      private   final  LinkedList hardCache  =   new  LinkedList();  
      /**  Reference queue for cleared SoftReference objects.  */    
      private  ReferenceQueue queue  =   new  ReferenceQueue();  
      // Strong Reference number  
      public  SoftHashMap()  {  this ( 100 ); }    
      public  SoftHashMap( int  hardSize)  { HARD_SIZE  =  hardSize; }    
             public  Object get(Object key)  {  
       Object result  =   null ;  
        //  We get the SoftReference represented by that key  
       SoftReference soft_ref  =  (SoftReference)hash.get(key);  
        if  (soft_ref  !=   null )  {  
          //  From the SoftReference we get the value, which can be  
          //  null if it was not in the map, or it was removed in  
          //  the processQueue() method defined below  
        result  =  soft_ref.get();  
          if  (result  ==   null )  {  
            //  If the value has been garbage collected, remove the  
            //  entry from the HashMap.  
           hash.remove(key);  
         }   else   {  
            //  We now add this object to the beginning of the hard  
            //  reference queue.  One reference can occur more than  
            //  once, because lookups of the FIFO queue are slow, so  
            //  we don't want to search through it each time to remove  
            //  duplicates.  
              // keep recent use object in memory  
           hardCache.addFirst(result);  
            if  (hardCache.size()  >  HARD_SIZE)  {  
              //  Remove the last entry if list longer than HARD_SIZE  
             hardCache.removeLast();  
           }    
         }    
       }    
        return  result;  
     }    
    
      /**  We define our own subclass of SoftReference which contains  
           not only the value but also the key to make it easier to find  
           the entry in the HashMap after it's been garbage collected.  */    
        private   static   class  SoftValue  extends  SoftReference  {  
        private   final  Object key;  //  always make data member final  
        /**  Did you know that an outer class can access private data  
             members and methods of an inner class?  I didn't know that!  
             I thought it was only the inner class who could access the  
             outer class's private information.  An outer class can also  
             access private members of an inner class inside its inner  
             class.  */    
        private  SoftValue(Object k, Object key, ReferenceQueue q)  {  
          super (k, q);  
          this .key  =  key;  
       }    
     }    
    
      /**  Here we go through the ReferenceQueue and remove garbage  
           collected SoftValue objects from the HashMap by looking them  
           up using the SoftValue.key data member.  */    
      public   void  processQueue()  {  
       SoftValue sv;  
        while  ((sv  =  (SoftValue)queue.poll())  !=   null )  {  
            if (sv.get() ==   null ) {  
               Log.e( " processQueue " ,  " null " );  
           } else {  
               Log.e( " processQueue " ,  " Not null " );  
           }  
         hash.remove(sv.key);  //  we can access private data!  
         Log.e( " SoftHashMap " ,  " release  "   +  sv.key);  
       }    
     }    
      /**  Here we put the key, value pair into the HashMap using  
           a SoftValue object.  */    
      public  Object put(Object key, Object value)  {  
       processQueue();  //  throw out garbage collected values first  
       Log.e( " SoftHashMap " ,  " put into  "   +  key);  
        return  hash.put(key,  new  SoftValue(value, key, queue));  
     }    
      public  Object remove(Object key)  {  
       processQueue();  //  throw out garbage collected values first  
        return  hash.remove(key);  
     }    
      public   void  clear()  {  
       hardCache.clear();  
       processQueue();  //  throw out garbage collected values  
      hash.clear();  
    }    
     public   int  size()  {  
      processQueue();  //  throw out garbage collected values first  
       return  hash.size();  
    }    
     public  Set entrySet()  {  
       //  no, no, you may NOT do that!!! GRRR  
       throw   new  UnsupportedOperationException();  
    }  
  }
相關文章
相關標籤/搜索