編寫對GC友好,又不泄漏的代碼

來源:互聯網
上載者:User

   作者:江南白衣,最新版連結:http://blog.csdn.net/calvinxiu/archive/2007/05/22/1621051.aspx,著作權,轉載請保留原文連結。

    看到JavaOne2007上有篇《Garbage-Collection-Friendly Programming》的68頁PPT,心想都2007了還談這個基本問題,一定總結得很全面了才好意思站出來講吧。    

GC的基礎概念見上篇:JDK5.0垃圾收集最佳化之--Don't Pause  

1.使用更多生命週期短的、小的、不改變指向(immutable)的對象,編寫清晰的代碼。

    出於懶惰也好,樸素的節儉意識也好,我們都習慣對一個變數重用再重用。但是....

  • Java的垃圾收集器喜歡短生命週期的對象,對象如果在新生代內,在垃圾收集發生前就死掉了,垃圾收集器就什麼都不用做了。
  • 現代JVM構建一個新對象只需要10個本地CPU指令,並不弱於C/C++。 (但垃圾收集沒有壓縮演算法時會稍慢,更頻繁的New對象也導致更頻繁的GC)。
  • 大對象的分配效率更低,而且對非壓縮演算法的垃圾收集器,更容易造成片段。
  • 對象重用增加了代碼的複雜度,降低了可讀性。

   所以有標題的呼籲,比如不要害怕為中間結果分配小對象。但編程習慣的改變也不是一朝一夕的事情。

2.將用完的對象設為NULL其實沒什麼作用。

    貌似很酷的把對象主動設為Null 的"好習慣"其實沒什麼用,JIT Compiler會自動分析local變數的生命週期。
    只有一個例外情況,就是String[1024] foo 這種赤裸裸的數組,你需要主動的foo[100]=null釋放第100號元素,所以最好還是直接用ArrayList這些標準庫算了。

 

3.避免顯式GC--System.gc()。

    大家都知道System.gc()不好,full-gc浪費巨大,gc的時機把握不一定對等等,甚至有-XX:+DisableExplicitGC的JVM參數來禁止它。

    哈哈,但我還不會用System.gc()呢,不怕不怕。真的不怕嗎?

  • 先用FindBugs 查一下所用到的全部第三方類庫吧...
  • 至少RMI 就會老實不客氣的執行System.gc()來實現分布式GC演算法。但我也不會用RMI啊。那EJB呢,EJB可是建在RMI上的....

    如果無可避免,用-Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 (單位為微妙) 增大大GC的間隔(原預設值為1分鐘),-XX:+ExplicitGCInvokesConcurrent 讓System.gc() 也CMS並發執行。

 

4.繼續千夫所指的finalize()

    大家也都知道finalize()不好,分配代價昂貴,釋放代價更昂貴(要多走一個迴圈,而且他們死得慢,和他們相關聯的對象也跟著死得慢了),又不確定能否被調用(JVM開始關閉時,就不會再進行垃圾收集),又不確定何時被調用(GC時間不定,即使system.gc()也只是提醒而不是強迫GC,又不確定以什麼樣的順序調用,所以finalize不是C++的解構函式,也不像C++的解構函式。

   我們都知道啊,所以我從來都沒使用。都是在顯式的維護那些外部資源,比如在finally{}裡釋放。

 

5.WeakReference/SoftReference

   這是個平時不怎麼會搭理,偶然知道了又覺得有用的Java特徵。大家都知道Java裡所有對象除int等基本類型外,都是Pass by Reference的指標,執行個體只要被一個對象連著,就不會被收集。
    而WeakReference就是真正意義上的C++指標,只是單純的指向一個對象,而不會影響對象的引用計數。
    而SoftReference更特別,在記憶體足夠時,對象會因為SoftReference的存在而不被收集,但記憶體不足時,對象就還是會被收集,怎麼看都是做簡單緩衝的料子。代碼如下:

  Foo foo = new Foo(); 
  SoftReference sr= new SoftReference(foo); 
  Foo bar =  sr.get(); 

  如果foo已被垃圾收集,sr.get()會返回Null;

  另外還有一個ReferenceQueue的機制,使得對象被回收時能獲得通知,比finalize()完全不知道GC何時會執行要聰明的多。

  ReferenceQueue rq = new ReferenceQueue();
  ref = new WeakReference(foo, rq); 
  WeakReference cleaned = rq.pool(); 

  cleaned就是剛剛被GC掉的WeakReference。

6.記憶體流失

   java 不是有垃圾收集器了嗎?怎麼還泄漏啊,唬我啊??
   嗯,此泄漏非比泄漏。C/C++的泄漏,是對象已不可到達,而記憶體又沒有回收,真正的記憶體黑洞。
   而Java的泄漏,則是因為各種原因,對象對應用已經無用,但一直被持有,一直可到達。
   總結原因無外乎幾方面:

  1. 被生命週期極長的集合類不當持有,號稱是Java記憶體流失的首因。
    這些集合類的生命週期通常極長,而且是一個輔助管理性質的對象,在一個業務事務運行完後,如果沒有將某個業務對象主動的從中清除的話,這個集合就會吃越來越多記憶體,可以用WeakReference,如WeakHashMap,使得它持有的對象不增加對象的引用數。
  2. Scope定義不對,這個很簡單了,方法的局部變數定義成類的變數,類的靜態變數等。
  3. 異常時沒有加finally{}來釋放某些資源,JDBC時代也是很普遍的事情。
  4. 另外一些我瞭解不深的原因,如:Swing裡的Listener沒有顯式remove;內部類持有外部對象的隱式引用;Finalizers造成關聯對象沒有被及時清空等。

記憶體流失的檢測

有不少工具輔助做這個事情的,如果手上一個工具也沒有,可以用JDK內建的小工具:

  • 看看誰佔滿了Heap?
    用JDK6的jmap可以顯示運行程式中對象的類型,個數與所佔的大小
    先用jps 找到進程號,然後jmap -histo pid 顯示或 jmap -dump:file=heap_file_name pid 匯出heap檔案
  • 為什麼這些對象仍然可以到達?
    用jhat(Java Heap Analysis Tool) 分析剛才匯出的heap檔案。
    先jhat heap_file_name,然後開啟瀏覽器http://localhost:7000/ 瀏覽。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.