1. 程式人生 > >Android中引起記憶體洩露的原因分析

Android中引起記憶體洩露的原因分析

昨天晚上,通過Android Studio的記憶體分析工具Android Monitor分析到我寫的一個照片選擇類出現了記憶體洩露,還挺嚴重的。雖沒造成oom 之類的crash,但是身為一個有程式碼潔癖症的程式設計師,並且一直對記憶體洩露頗有研究的我,我決定還是要找到出現記憶體洩露的原因,從頭看程式碼,看了一個多小時,總算找到了。

先說說記憶體溢位和記憶體洩露的區別。

記憶體溢位就是oom,意思是往記憶體裡放的資料太多了,記憶體裝不下了,就溢位了。就像你往杯子裡裝水一樣,杯子就那麼大,你還是一直往裡面裝,自然會溢位。

而記憶體洩露呢,就是一個物件你都不用它了,但是它還是在記憶體裡賴著不走,java虛擬機器就算執行了GC(記憶體回收),它也一樣在記憶體裡賴著不走。就像辦公室的冰箱,會有清潔工去經常清理,把不用的東西給扔掉。比如有個塑料袋裝的饅頭,是個已離職的同事的,確實沒人要了,但是上面貼了個標籤寫的是:“此饅頭不要扔”。而清潔工看到這個標籤,以為還有人要呢,就沒清理。於是這個饅頭就造成了冰箱空間的洩露。

好的程式設計師一定要注意防止記憶體洩露,那麼我們怎麼防止記憶體洩露呢?

剛好在網上看到一篇文章,覺得總結的挺好的,但是原文連結已經沒了,所以我就整理一下貼過來:

1、靜態集合類

宣告為靜態(static)的HashMap、Vector 等集合類的使用最容易引起記憶體洩漏,因為這些靜態變數的生命週期與應用程式一致,如果該HashMap是靜態的,那麼它將一直存在,而其中所有的Object物件也不能被釋放,因為它們也將一直被該HashMap引用著。

2、 監聽器(觀察者模式) 
在java 程式設計中,我們都需要和監聽器打交道,通常一個應用當中會用到很多監聽器,比如觀察者模式,我們會呼叫一個控制元件的諸如addClickListener()等方法來增加監聽器,但往往在釋放物件的時候卻沒有記住去remove這些監聽器,從而增加了記憶體洩漏的機會。

3、物理連結,資料庫操作
一些物理連線,比如資料庫連線和網路連線,除非其顯式的關閉了連線,否則是不會自動被GC 回收的。例如Java 資料庫連線一般用DataSource.getConnection()來建立,當不再使用時必須用Close()方法來釋放,因為這些連線是獨立於JVM的。其中對於Resultset和Statement物件可以不進行顯式回收,但Connection一定要顯式回收,因為Connection在任何時候都無法自動回收,而Connection一旦回收,Resultset和Statement物件就會立即為NULL。但是如果使用連線池,情況就不一樣了,除了要顯式地關閉連線,還必須顯式地關閉Resultset和Statement物件(關閉其中一個,另外一個也會關閉),否則就會造成大量的Statement 物件無法釋放,從而引起記憶體洩漏。

4、內部類和外部模組等的引用 
內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類物件沒有釋放。對於程式設計師而言,自己的程式很清楚,如果發現記憶體洩漏,自己對這些物件的引用可以很快定位並解決,但是現在的實際開發過程中往往並非一個人實現,模組化的思想在現代軟體中非常明顯,所以程式設計師要小心外部模組不經意的引用,例如程式設計師A 負責A 模組,呼叫了B 模組的一個方法如: 
public void registerMsg(Object b); 
這種呼叫就要非常小心了,傳入了一個物件,很可能模組B就保持了對該物件的引用,這時候就需要注意模組B 是否提供相應的操作去除引用。

Android裡面最容易發生的內部類導致的記憶體洩露就是Handler,在Activity裡面定義了一個private Handler,此時在裡面引用了Activity,就很容易引起記憶體洩露。

我昨天晚上遇到的問題就是上面寫到的第三個,是使用了資料庫,但是沒關閉cursor,具體程式碼如下,紅色的框裡面的關閉程式碼是我後來修復時加的,之前沒有:


===============================
如果你覺得幫到了你,請給作者打賞一口飯吃: