我記得在有一次面試中,面試官問我自己實作的一個棧中會不會有記憶體洩露的問題,我努力搜尋可能的問題,就是感受不到可能出現的問題。當時忽然意識到,記憶體洩露這個問題一直被我忽略,因為用的是java/c#,這些語言中都有記憶體自動回收的機制,我突然發現自己對這個問題竟然一無所知。面試中的棧就是下面這個:


這段程式不管你怎麼測試都是沒有問題的,但是他确實可能引起“記憶體洩露”。定位到pop()函數,在return語句中,當我們彈出一個元素時,隻是簡單的讓棧頂指針(size)-1。邏輯上,棧中的這個元素已經彈出,已經沒有用了。但是事實上,被彈出的元素依然存在于elements數組中,它依然被elements數組所引用,gc是無法回收被引用着的對象的。也許你期望等這整個棧失去引用(将被gc回收時),棧内的elements數組一起被gc回收。但是實際的使用過程中,又有誰能夠預料到這個棧會存活多長時間。為了保險起見,我們需要在彈出一個元素的時候,就讓這個元素失去引用,便于gc回收。我們隻需要讓pop()函數彈出時,同時解除對彈出元素的引用即可。
從上面的例子中,我們可以發現當類中持有過期的元素的引用時,就有可能造成記憶體洩露問題。而且通常這種記憶體洩露問題都是我們無意識造成的,上面的棧中,邏輯上我們認為彈出的元素就應該被gc回收掉,但事實上gc沒有辦法回收,是以elements數組依然持有它。這種問題很隐蔽,通常隻要類自己管理記憶體(如類中有一個array或list型的結構),那麼我們就應該警惕記憶體洩露的問題。
記憶體洩露來源及解決
記憶體洩露可能來源于緩存。我們為了讓下次的程式的處理速度更快,常常需要将一些資訊緩存在記憶體中,但是這些過期的緩存又很容易被遺忘,進而使得它不再有用之後很長一段時間内仍然留在緩存中。例如像一個要顯示圖檔牆的程式,我們需要緩存圖檔和相關的資訊,為了友善gc回收過期的緩存,我們可以使用weakhashmap來實作緩存,當界面顯示圖檔的時候,界面持有相關圖檔的引用,這些引用同時也存在于weakhashmap中。而其他不被界面持有的過期緩存,則weakhashmap會自動将這些剔除。
總的說來,隻要在緩存之外存在對某個項的鍵的引用,該項就有意義,那麼就可以用weakhashmap代表緩存;當緩存中的項過期之後,它們就自動被删除。記住隻有當所有的緩存項的生命周期是由該鍵的外部引用而不是由值決定時,weakhashmap才有用處。
更為常見的情景則是,"緩存項的生命周期是否有意義"并不是非常容易确定,随着時間推移,其中的項會變得越來越沒有價值。在這種情況下,緩存應該是不是地清除掉沒有的項。這項清除工作可以由一個背景線程(可能是timer或者scheduledthreadpoolexecutor)來完成,或者也可以在給緩存添加新條目的時候順便進行清理。linkedhashmap類利用它的removeeldestentry方法可以很容易地實作後一種方案。對于更加複雜的緩存,必須直接使用java.lang.ref.
記憶體洩露的第三個常見來源是監聽器和其他回調。如果你實作了一個api,用戶端在這個api中注冊回調,卻沒有顯式地取消注冊,那麼除非你采取某些動作,否則他們就會積聚。確定回調立即被當成垃圾回收的最佳方法是隻儲存它們的弱引用(weak reference),例如,隻将它們儲存成weakhashmap中的鍵。
部分文字直接截取自《effective java》
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。
轉載:http://www.cnblogs.com/kissazi2/p/3618464.html