一個優秀的Java程序員必須瞭解GC的工做原理、如何優化GC的性能、如何與GC進行有限的交互,由於有一些應用程序對性能要求較高,例如嵌入式系統、實時系統等,只有全面提高內存的管理效率 ,才能提升整個應用程序的性能。
一個優秀的Java程序員必須瞭解GC的工做原理、如何優化GC的性能、如何與GC進行有限的交互,由於有一些應用程序對性能要求較高,例如嵌入式系統、實時系統等,只有全面提高內存的管理效率 ,才能提升整個應用程序的性能。本篇文章首先簡單介紹GC的工做原理以後,而後再對GC的幾個關鍵問題進行深刻探討,最後提出一些Java程序設計建議,從GC角度提升Java程序的性能。
GC的基本原理
Java的內存管理實際上就是對象的管理,其中包括對象的分配和釋放。
對於程序員來講,分配對象使用new關鍵字;釋放對象時,只要將對象全部引用賦值爲null,讓程序不可以再訪問到這個對象,咱們稱該對象爲\"不可達的\".GC將負責回收全部\"不可達\"對象的內存空間。
對於GC來講,當程序員建立對象時,GC就開始監控這個對象的地址、大小以及使用狀況。一般,GC採用有向圖的方式記錄和管理堆(heap)中的全部對象(詳見 參考資料1 )。經過這種方式肯定哪些對象是\"可達的\",哪些對象是\"不可達的\".當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
1 class MyObject{ 2 3 Test main; //記錄Test對象,在finalize中時用於恢復可達性 4 5 public MyObject(Test t) 6 7 { 8 9 main=t; //保存Test 對象 10 11 } 12 13 protected void finalize() 14 15 { 16 17 main.ref=this;// 恢復本對象,讓本對象可達 18 19 System.out.println(\"This is finalize\");//用於測試finalize只運行一次 20 21 } 22 23 } 24 25 class Test { 26 27 MyObject ref; 28 29 public static void main(String[] args) { 30 31 Test test=new Test(); 32 33 test.ref=new MyObject(test); 34 35 test.ref=null; //MyObject對象爲不可達對象,finalize將被調用 36 37 System.gc(); 38 39 if (test.ref!=null) System.out.println(\"My Object還活着\"); 40 41 } 42 43 } 44 45 運行結果: 46 47 This is finalize 48 49 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.如下給出這種引用類型的使用僞代碼;程序員
1 //申請一個圖像對象 2 3 Image image=new Image();//建立Image對象 4 5 … 6 7 //使用 image 8 9 … 10 11 //使用完了image,將它設置爲soft 引用類型,而且釋放強引用; 12 13 SoftReference sr=new SoftReference(image); 14 15 image=null; 16 17 … 18 19 //下次使用時 20 21 if (sr!=null) image=sr.get(); 22 23 else{ 24 25 //因爲GC因爲低內存,已釋放image,所以須要從新裝載; 26 27 image=new Image(); 28 29 sr=new SoftReference(image); 30 31 }
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程序的暫停時間。算法