自定義註釋

一個優秀的Java 程序員必須瞭解的GC 機制

一個優秀的Java 程序員必須瞭解GC 的 工作原理、如何優化GC 的性能、如何與GC 進 行有限的交互,有一些應用程序對性能要求較高,例如嵌入式系統、實時系統等,只有全面提升內存的管理效率,才能提高整個應用程序的性能。本文將從GC 的工作原理、GC 的幾個關鍵問題進行 探討,最後提出一些Java 程序設計建議,如何從GC 角度提高Java 程序的性能。 

    GC 的基本原理 
   
    GC 是什麼? 爲什麼要有GC 呢? 
    GC 是垃圾收集的意思(Garbage Collection),內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java 提供的GC 功能可以自動監測對象是 否超過作用域從而達到自動回收內存的目的,Java 語言沒有提供釋放已分配內存的顯示操作方 法。 

    所以,Java 的內存管理實際上就是對象的管理,其中包括對象的分配和釋 放。 

    對於程序員來說,分配對象使用new關鍵字;釋放對象時,只要將對象所有引用賦值爲null,讓程序不能夠再訪問到這個對象,我們稱該對象爲"不可達 的".GC 將負責回收所有"不可達"對象的內存空間。 

    對於GC 來說,當程序員創建對象時,GC 就開始監控這個對象的地址、大小以及使用情況。通常,GC 採 用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達的",哪些對象是"不可達的".當GC 確定一些對象爲"不可達"時,GC 就有 責任回收這些內存空間。但是,爲了保證 GC 能夠在不同平臺實現的問題,Java 規範對GC 的很多行爲都沒有進行嚴 格的規定。例如,對於採用什麼類型的回收算法、什麼時候進行回收等重要問題都沒有明確的規定。因此,不同的JVM的實現者往往有不同的實現算法。這也給Java 程序員的開發帶來行多不確定性。本文研究了幾個與GC 工 作相關的問題,努力減少這種不確定性給Java 程序帶來的負面影響。 

    增量式GC ( Incremental GC ) 

    GC 在JVM中通常是由一個或一組進程來實現的,它本身也和用戶程序一樣佔用heap空間,運行時也佔用CPU.當GC 進程運行時,應用程序停止運行。因此,當GC 運 行時間較長時,用戶能夠感到Java 程序的停頓,另外一方面,如果GC 運行時間太短,則可能對象回收率太低,這意味着還有很多應該回收的對象沒有被回收,仍然佔用大量內 存。因此,在設計GC 的時候,就必須在停頓時間和回收率之間進行權衡。一個好的GC 實現允許用戶定義自己所需要的設置,例如有些內存有限有設備,對內存的使用量非常敏感,希望GC 能夠準確的回收內存,它並不在意程序速度的放慢。另外一些實時網絡遊戲,就不能夠允許程序有長時間 的中斷。增量式GC 就是通過一定的回收算法,把一個長時間的中斷,劃分爲很多個小的中斷,通 過這種方式減少GC 對用戶程序的影響。雖然,增量式GC 在整體性能上可能不如普通GC 的效率 高,但是它能夠減少程序的最長停頓時間。 

    Sun JDK提供的HotSpot JVM就能支持增量式GC .HotSpot JVM缺省GC 方式爲不使用增量GC , 爲了啓動增量GC ,我們必須在運行Java 程 序時增加-Xincgc 的參數。HotSpot JVM增量式GC 的實現是採用Train GC 算法 。它的基本想法就是,將堆中的所有對象按照創建和使用情況進行分組(分層),將使用頻繁高和具 有相關性的對象放在一隊中,隨着程序的運行,不斷對組進行調整。當GC 運行時,它總是先回收 最老的(最近很少訪問的)的對象,如果整組都爲可回收對象,GC 將整組回收。這樣,每次GC 運行只回收一定比例的不可達對象,保證程序的順暢運行。 

    詳解finalize函數 

    finalize 是位於Object類的一個方法,該方法的訪問修飾符爲protected,由於所有類爲Object的子類,因此用戶類很容易訪問到這個方法。由 於,finalize函數沒有自動實現鏈式調用,我們必須手動的實現,因此finalize函數的最後一個語句通常是 super.finalize()。通過這種方式,我們可以實現從下到上實現finalize的調用,即先釋放自己的資源,然後再釋放父類的資源。 

    根據Java 語言規範,JVM保證調用finalize函數之前,這個對象 是不可達的,但是JVM不保證這個函數一定會被調用。另外,規範還保證finalize函數最多運行一次。 

    很多Java 初學者會認爲這個方法類似與C++中的析構函數,將很多對象、 資源的釋放都放在這一函數裏面。其實,這不是一種很好的方式。原因有三,其一,GC 爲了能夠 支持finalize函數,要對覆蓋這個函數的對象作很多附加的工作。其二,在finalize運行完成之後,該對象可能變成可達的,GC 還要再檢查一次該對象是否是可達的。因此,使用 finalize會降低GC 的運行性能。其三,由於GC 調用 finalize的時間是不確定的,因此通過這種方式釋放資源也是不確定的。 

    通常,finalize用於一些不容易控制、並且非常重要資源的釋放,例如一些I/O的操作,數據的連接。這些資源的釋放對整個應用程序是非常關鍵的。在 這種情況下,程序員應該以通過程序本身管理(包括釋放)這些資源爲主,以finalize函數釋放資源方式爲輔,形成一種雙保險的管理機制,而不應該僅僅 依靠finalize來釋放資源。 

    下面給出一個例子說明,finalize函數被調用以後,仍然可能是可達的,同時也可說明一個對象的finalize只可能運行一次。 

   
[java] view plain copy
  1. class MyObject{  
  2.        Test main; //記錄Test對象,在finalize中時用於恢復可達性  
  3.        public MyObject(Test t)  
  4.        {  
  5.           main=t; //保存Test 對象  
  6.        }  
  7.        protected void finalize()  
  8.        {  
  9.           main.ref=this;// 恢復本對象,讓本對象可達  
  10.           System.out.println("This is finalize");//用於測試finalize只運行一次  
  11.        }  
  12. }   


      
[java] view plain copy
  1. class Test {  
  2.        MyObject ref;  
  3.        public static void main(String[] args)  
  4.         {  
  5.           Test test=new Test();  
  6.           test.ref=new MyObject(test);  
  7.           test.ref=null//MyObject對象爲不可達對象,finalize將被調用  
  8.           System.gc();  
  9.           if (test.ref!=null) System.out.println("My Object還活着");  
  10.       }  
  11. }   


      運行結果: 
    This is finalize 

    MyObject還活着:此例子中,需要注意的是雖然MyObject對象在finalize中變成可達對象,但是下次回收時候,finalize卻不再 被調用,因爲finalize函數最多隻調用一次。 

    程序如何與GC 進行交互 

    Java2 增強了內存管理功能,增加了一個java .lang.ref 包,其中定義了三種引用類。這三種引用類分別爲SoftReference、 WeakReference和 PhantomReference.通過使用這些引用類,程序員可以在一定程度與GC 進行交 互,以便改善GC 的工作效率。這些引用類的引用強度介於可達對象和不可達對象之間。 

    創建一個引用對象也非常容易,例如如果你需要創建一個Soft Reference對象,那麼首先創建一個對象,並採用普通引用方式(可達對象);然後再創建一個SoftReference引用該對象;最後將普通引用 設置爲null.通過這種方式,這個對象就只有一個Soft Reference引用。同時,我們稱這個對象爲Soft Reference 對象。 

    Soft Reference的主要特點是據有較強的引用功能。只有當內存不夠的時候,才進行回收這類內存,因此在內存足夠的時候,它們通常不被回收。另外,這些引 用對象還能保證在Java 拋出OutOfMemory 異常之前,被設置爲null.它可以用於實現一些常用圖片的緩存,實現Cache的功能,保證最大限度的使用內存而不引起OutOfMemory.以下給 出這種引用類型的使用僞代碼; 



//申請一個圖像對象 
  Image image=new Image();//創建Image對象 
  … 
  //使用 image 
  … 
  //使用完了image,將它設置爲soft 引用類型,並且釋放強引用; 
  SoftReference sr=new SoftReference(image); 
  image=null; 
   … 
   //下次使用時 
   if (sr!=null) image=sr.get(); 
   else{ 
   //由於GC 由於低內存,已釋放image,因此需要重新裝載; 
   image=new Image(); 
  sr=new SoftReference(image); 
  } 
    Weak 引用對象與Soft引用對象的最大不同就在於:GC 在進行回收 時,需要通過算法檢查是否回收Soft引用對象,而對於Weak引用對象,GC 總是進行回 收。Weak引用對象更容易、更快被GC 回收。雖然,GC 在運行時一定回收Weak對象,但是複雜關係的Weak對象羣常常需要好幾次 GC 的運行才能完成。Weak引用對象常常用於Map結構中,引用數據量較大的對象,一旦該對象的強引用 爲null時,GC 能夠快速地回收該對象空間。 

    Phantom 引用的用途較少,主要用於輔助finalize函數的使用。Phantom對象指一些對象,它們執行完了finalize函數,併爲不可達對象,但是它們 還沒有被GC 回收。這種對象可以輔助finalize進行一些後期的回收工作,我們通過覆蓋 Reference的clear()方法,增強資源回收機制的靈活性。 



    一些Java 編程的建議 

    根據GC 的工作原理,我們可以通過一些技巧和方式,讓GC 運行更加有效率,更加符合應用程序的要求。一些關於程序設計的幾點建議: 

  1.最基本的建議就是儘早釋放無用對象的引用。大多數程序員在使用臨時變量的時候,都是讓引用變量在退出活動域(scope)後,自動設置爲 null.我們在使用這種方式時候,必須特別注意一些複雜的對象圖,例如數組,隊列,樹,圖等,這些對象之間有相互引用關係較爲複雜。對於這類對象,GC 回收它們一般效率較低。如果程序允許,儘早將不用的引用對象賦爲null.這樣可以加速GC 的工作。 

  2.儘量少用finalize函數。finalize函數是Java 提供給程序 員一個釋放對象或資源的機會。但是,它會加大GC的工作量,因此儘量少採用finalize 方式回收資源。 

  3.如果需要使用經常使用的圖片,可以使用soft應用類型。它可以儘可能將圖片保存在內存中,供程序調用,而不引起 OutOfMemory. 

  4.注意集合數據類型,包括數組,樹,圖,鏈表等數據結構,這些數據結構對GC 來 說,回收更爲複雜。另外,注意一些全局的變量,以及一些靜態變量。這些變量往往容易引起懸掛對象(dangling reference),造成內存浪費。 
  5.當程序有一定的等待時間,程序員可以手動執行System.gc (),通知GC 運行,但是Java 語言規範並不保證GC 一定會執行。使用增量式GC 可以縮短Java 程序的暫停時間
發佈了63 篇原創文章 · 獲贊 8 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章