常見原因
1.集合類
集合類如果僅僅有添加元素的方法,而沒有相應(yīng)的刪除機制,導(dǎo)致內(nèi)存被占用。如果這個集合類是全局性的變量 (比如類中的靜態(tài)屬性,全局性的 map 等即有靜態(tài)引用或 final 一直指向它),那么沒有相應(yīng)的刪除機制,很可能導(dǎo)致集合所占用的內(nèi)存只增不減。
2.單例模式
不正確使用單例模式是引起內(nèi)存泄露的一個常見問題,單例對象在被初始化后將在 JVM 的整個生命周期中存在(以靜態(tài)變量的方式),如果單例對象持有外部對象的引用,那么這個外部對象將不能被 JVM 正?;厥?,導(dǎo)致內(nèi)存泄露
3.Android 組件或特殊集合對象的使用
BraodcastReceiver,ContentObserver,F(xiàn)ileObserver,Cursor,Callback等在 Activity onDestroy 或者某類生命周期結(jié)束之后一定要 unregister 或者 close 掉,否則這個 Activity 類會被 system 強引用,不會被內(nèi)存回收。
不要直接對 Activity 進行直接引用作為成員變量,如果不得不這么做,請用 private WeakReference mActivity 來做,相同的,對于Service 等其他有自己聲明周期的對象來說,直接引用都需要謹慎考慮是否會存在內(nèi)存泄露的可能。
4. Handler
要知道,只要 Handler 發(fā)送的 Message 尚未被處理,則該 Message 及發(fā)送它的 Handler 對象將被線程 MessageQueue 一直持有。由于 Handler 屬于 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的。因此這種實現(xiàn)方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導(dǎo)致無法正確釋放。如上所述,Handler 的使用要尤為小心,否則將很容易導(dǎo)致內(nèi)存泄露的發(fā)生。
5. Thread 內(nèi)存泄露
線程也是造成內(nèi)存泄露的一個重要的源頭。線程產(chǎn)生內(nèi)存泄露的主要原因在于線程生命周期的不可控。比如線程是 Activity 的內(nèi)部類,則線程對象中保存了 Activity 的一個引用,當線程的 run 函數(shù)耗時較長沒有結(jié)束時,線程對象是不會被銷毀的,因此它所引用的老的 Activity 也不會被銷毀,因此就出現(xiàn)了內(nèi)存泄露的問題。
6.一些不良代碼造成的內(nèi)存壓力
有些代碼并不造成內(nèi)存泄露,但是它們,或是對沒使用的內(nèi)存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內(nèi)存。
6.1Bitmap 沒調(diào)用 recycle().
Bitmap 對象在不使用時,我們應(yīng)該先調(diào)用 recycle() 釋放內(nèi)存,然后才它設(shè)置為 null. 因為加載 Bitmap 對象的內(nèi)存空間,一部分是 java 的,一部分 C? ?的(因為 Bitmap 分配的底層是通過 JNI 調(diào)用的 )。 而這個 recyle() 就是針對 C 部分的內(nèi)存釋放。
6.2構(gòu)造 Adapter 時,沒有使用緩存的 convertView。
以業(yè)務(wù)測試過程中常見的部分內(nèi)存泄露實例來說明:
1. callback 只有 add 操作,沒有注銷 remove.
從引用關(guān)系可以看到當前 view 被 callback 引用,而 callback 被外部對象 sharkprotocolQueue 持有引用而導(dǎo)致泄漏。
2. 發(fā)送延時消息時,如果該消息未處理,在退出頁面后會導(dǎo)致該頁面無法回收。
Android 應(yīng)用啟動的時候會創(chuàng)建 UI 主線程的 Looper 對象,它存在于整個應(yīng)用的生命周期,用于處理消息隊列里的 Message。而這些 Message 會引用發(fā)送該消息的 Handler 對象。
那么問題來了,如果這些 Handler 是 Activity 的內(nèi)部類,那么當這些 Handler 的消息未處理完或者消息本身是延時消息的話,就會導(dǎo)致 Activity 退出后,從 Activity 到 Handler 到 Message 到 Looper 的引用鏈條一直存在,從而導(dǎo)致 Activity 的泄露!
3. 異步線程未完成前退出 Activity 等組件,可能會導(dǎo)致界面資源無法釋放。
這種情況是典型的線程對象導(dǎo)致的內(nèi)存泄露。原因也很簡單,線程 Thread 對象的 run 任務(wù)未執(zhí)行完之前,對象本身是不會釋放的。因此 Activity 等組件對象內(nèi)的線程對象成員如果有耗時任務(wù)(一般也都是耗時任務(wù)),就會導(dǎo)致一直持有組件本身的引用內(nèi)存泄露!
本文部分內(nèi)容和經(jīng)驗摘自網(wǎng)絡(luò),結(jié)合本次內(nèi)存泄露的排查總結(jié)予以歸納。
優(yōu)秀實踐
對 Activity 等組件的引用應(yīng)該控制在 Activity 的生命周期之內(nèi); 如果不能就考慮使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部長生命周期的對象引用而泄露。
在代碼復(fù)審的時候關(guān)注長生命周期對象:全局性的集合、單例模式的使用、類的 static 變量等等。
盡量不要在靜態(tài)變量或者靜態(tài)內(nèi)部類中使用非靜態(tài)外部成員變量(包括context ),即使要使用,也要考慮適時把外部成員變量置空;也可以在內(nèi)部類中使用弱引用來引用外部類的變量。
Handler 的持有的引用對象最好使用弱引用,資源釋放時也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的時候,取消掉該 Handler 對象的 Message和 Runnable.
removeCallbacks(Runnable r) 或r emoveMessages(int what),或 removeCallbacksAndMessages(null)等。
線程 Runnable 執(zhí)行耗時操作,注意在頁面返回時及時取消或者把 Runnable 寫成靜態(tài)類。
a) 如果線程類是內(nèi)部類,改為靜態(tài)內(nèi)部類。
b) 線程內(nèi)如果需要引用外部類對象如 context,需要使用弱引用。
在 Java 的實現(xiàn)過程中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦空,如清空對圖片等資源有直接引用或者間接引用的數(shù)組(使用 array.clear() ; array = null),最好遵循誰創(chuàng)建誰釋放的原則。
http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=125&extra=page%3D2