程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> J2EE >> 編寫對GC友好又不洩漏的代碼

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

編輯:J2EE

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的洩漏,則是因為各種原因,對象對應用已經無用,但一直被持有,一直可到達。
   總結原因無外乎幾方面:

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

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

看看誰占滿了Heap?
用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/ 浏覽。


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved