Android內(nèi)存泄漏導致原因深入探究
什么是內(nèi)存泄露
什么是內(nèi)存泄露,通俗的來說就是堆中的一些對象已經(jīng)不會再被使用了,但垃圾收集器卻無法將它們從內(nèi)存中清除。
內(nèi)存泄漏很嚴重的問題,因為它會阻塞內(nèi)存資源并隨著時間的推移降低系統(tǒng)性能。如果不進行有效的處理,最終的結(jié)果將會使應(yīng)用程序耗盡內(nèi)存資源,無法正常服務(wù),導致程序崩潰,拋出java.lang.OutOfMemoryError異常。
堆內(nèi)存中通常有兩種類型的對象:被引用的對象和未被引用的對象。被引用的對象是應(yīng)用程序中仍然具有活躍的引用,而未被引用的對象則沒有任何活躍的引用。
垃圾收集器會回收那些未被引用的對象,但不會回收那些還在被引用的對象。這也是內(nèi)存泄露發(fā)生的源頭。
哪些操作會造成內(nèi)存泄漏
下面我們介紹幾種常見的造成內(nèi)存泄露的情況
1、意外聲明全局變量是最常見也最容易修復的內(nèi)存泄漏問題,比如:
function fn() {
name = '張三';
}
解釋器在解釋上面的函數(shù)時,會把name當做全局變量,即window.name = ‘張三’。只要window對象沒有被清理,那么name屬性和屬性值將一直存在,造成內(nèi)存泄露。
解決方法:
(1)只要在變量聲明前面加上var、let或const關(guān)鍵字即可,這樣變量就會在函數(shù)執(zhí)行完畢后離開作用域。
(2)使用this關(guān)鍵字
function fn() {
this.name = '張三';
}
(3)可以在 JavaScript 文件開頭添加 “use strict”,使用嚴格模式。這樣在嚴格模式下解析 JavaScript 可以防止意外的全局變量
(4)在使用完之后,對其賦值為null或者重新分配
2、 定時器導致的泄露
let name = '張三';
setInterval(() => {
console.log(name);
}, 100);
上面的代碼中,只要定時器一直運行,回調(diào)函數(shù)中引用的name就會一直占用內(nèi)存。
3、閉包、控制臺日志、循環(huán)(在兩個對象彼此引用且彼此保留時,就會產(chǎn)生一個循環(huán)),下面我們看一個JavaScript閉包導致的內(nèi)訓泄露例子
let fun = function() {
let name = '張三';
return function() {
return name;
};
};
調(diào)用fun()會導致分配給name的內(nèi)存被泄漏。以上代碼執(zhí)行后創(chuàng)建了一個內(nèi)部閉包,只要返回的函數(shù)存在就不能清理name,因為閉包一直在引用著它。
常見內(nèi)存泄露問題
1.資源性對象未關(guān)閉
資源性對象(如Cursor、File等一些Closeable對象),它們往往使用了緩沖區(qū),緩沖區(qū)不僅在JVM內(nèi),JVM之外也有。如果僅僅把變量設(shè)置為null,而不關(guān)閉它們,緩沖區(qū)得不到釋放,往往造成內(nèi)存泄露。
解決方案:一般在finally中關(guān)閉資源型對象,而后設(shè)置對象為null
2.注冊對象未注銷
訂閱者模式中,如果注冊對象不再使用時,未及時注銷,會導致訂閱者列表中維持這對象的引用,阻止垃圾回收,導致內(nèi)存泄露。常見場景:動態(tài)注冊BroadcastReceiver,注冊PhoneStateListener,注冊EventBus等等,
還有自定義使用訂閱者模式的情形。
解決方案:一般在onDestroy()中進行解注冊
3.非靜態(tài)內(nèi)部類的靜態(tài)實例
非靜態(tài)內(nèi)部類持有外部類實例的引用,若非靜態(tài)內(nèi)部類的實例是靜態(tài)的,便擁有app存活期整個生命周期,長期持有外部類的引用,阻止外部類實例被回收。
使用內(nèi)部類的情況十分常見,尤其是匿名內(nèi)部類:一些接口的匿名實現(xiàn)類,都是內(nèi)部類。
解決方案:(1)改為靜態(tài)內(nèi)部類,不再持有外部類實例的引用 (2)避免申明非靜態(tài)內(nèi)部類的靜態(tài)實例 (3)將內(nèi)部類抽取出來封裝成一個單例,如果需要Context,沒有特殊要求就使用Application Context;如果需要Activity Context,則使用完畢置空,或者使用弱引用
4.單例模式引起的內(nèi)存泄露
由于單例模式的靜態(tài)特性,使得它的生命周期和我們的應(yīng)用一樣長,如果讓單例無限制的持有Activity的強引用就會導致內(nèi)存泄漏
解決方案:使用Activity的弱引用,或者沒特殊需求時使用Application Context
5.Handler臨時性內(nèi)存泄露
非靜態(tài)Handler持有Activity或Service的引用,Message中的target指向Handler實例,所以當Message在MessageQueue中排隊,長時間未得到處理時,Activity邊不會被回收,導致臨時性內(nèi)存泄露。
解決方案:(1)使用靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象(Activity或Service)使用弱引用 (2)在onDestroy()中移除消息隊列中的消息 mHandler.removeCallbacksAndMessages(null)
類似的:AsyncTask內(nèi)部也是Handler機制,也存在同樣的臨時性內(nèi)存泄露風險
6.容器中對象未及時清理導致內(nèi)存泄露
容器類一般擁有較長的生命周期,若內(nèi)部不再使用的對象不及時清理,內(nèi)部對象邊一直被容器類引用。上述2中的訂閱者列表也屬于容器類這中情況。另外常見的容器類還有線程池、對象池、圖片緩存池等。線程池中的線程若存在ThreadLocal對象,因為線程對象一直被循環(huán)使用,ThreadLocal對象便會一直被引用,要注意對value對象的置空釋放。
7.靜態(tài)View導致內(nèi)存泄露
有時,當一個Activity經(jīng)常啟動,但是對應(yīng)的View讀取非常耗時,我們可以通過靜態(tài)View變量來保持對該Activity的rootView引用。這樣就可以不用每次啟動Activity都去讀取并渲染View了。這確實是一個提高Activity啟動速度的好方法!但是要注意,一旦View attach到我們的Window上,就會持有一個Context(即Activity)的引用。而我們的View有事一個靜態(tài)變量,所以導致Activity不被回收。 解決辦法:在使用靜態(tài)View時,需要確保在資源回收時,將靜態(tài)View detach掉。
8.屬性動畫未及時關(guān)閉導致內(nèi)存泄露
在使用ValueAnimator或者ObjectAnimator時,如果沒有及時做cancel取消動畫,就可能造成內(nèi)存泄露。 因為在cancel方法里,最后調(diào)用了endAnimation(); ,在endAnimation里,有個AnimationHandler的單例,會持有屬性動畫對象的引用
解決辦法:在在onDestory時,調(diào)用動畫的cancel方法
9.WebView內(nèi)存泄露
目前Android中WebView的實現(xiàn)存在很大的兼容性問題,Google支持各個ROM廠商自行定制自己的WebView實現(xiàn),各個ROM間差異較大,且大多都存在內(nèi)存泄露問題。除了調(diào)用其內(nèi)部的clearCache()、clearHistory()、removeAllViews()、freeMemory()、destroy()和置null以外,一般比較粗暴有效的解決方法是:將包含WebView的Activity放在一個單獨的進程中,不需要時將進程銷毀,從而釋放所有所占內(nèi)存。
10.其他的系統(tǒng)控件以及自定義View
在 Android Lollipop 之前使用 AlertDialog 可能會導致內(nèi)存泄漏
view中有線程或者動畫 要及時停止。這是為了防止內(nèi)存泄漏,可以在onDetachedFromWindow方法中結(jié)束,這個方法回調(diào)的時機是 當View的Activity退出或者當前View被移除的時候 會調(diào)用 這時候是結(jié)束動畫或者線程的好時機 另外還有一個對應(yīng)的方法 onAttachedToWindow 這個方法調(diào)用的時機是在包含View的Activity啟動時 回調(diào) 回調(diào)在onDraw方法之前
11.其他常見的引起內(nèi)存泄漏原因
- (1)構(gòu)造Adapter時,沒有使用緩存的 contentView
- (2)Bitmap在不使用的時候沒有使用recycle()釋放內(nèi)存
- (3)警惕線程未終止造成的內(nèi)存泄露;譬如在Activity中關(guān)聯(lián)了一個生命周期超過Activity的Thread,在退出Activity時切記結(jié)束線程。一個典型的例子就是HandlerThread的run方法是一個死循環(huán),它不會自己結(jié)束,線程的生命周期超過了Activity生命周期,我們必須手動在Activity的銷毀方法中調(diào)用thread.getLooper().quit();才不會泄露
- (4)避免代碼設(shè)計模式的錯誤造成內(nèi)存泄露;譬如循環(huán)引用,A持有B,B持有C,C持有A,這樣的設(shè)計誰都得不到釋放
文末
理解內(nèi)存泄漏的危害,我們舉個簡單的例子。有一個賓館,有100間房間,顧客每次都是在前臺進行登記,然后拿到房間鑰匙。如果有些顧客不需要該房間了,也不歸還鑰匙,久而久之,前臺處可用房間越來越少,收入也越來越少,瀕臨倒閉。當程序申請了內(nèi)存,而不進行歸還,久而久之,可用內(nèi)存越來越少,OS就會進行自我保護,殺掉該進程,這就是我們常說的OOM(out of memory)。
到此這篇關(guān)于Android內(nèi)存泄漏導致原因深入探究的文章就介紹到這了,更多相關(guān)Android內(nèi)存泄漏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何調(diào)用百度地圖API實現(xiàn)手機自動定位
api手機自動定位,通過聲明地址解析器,獲取當前坐標,如何調(diào)用百度地圖api實現(xiàn)手機自動定位呢?接下來,一起跟小編來學習吧。2015-09-09
Android中 視頻屏幕左半部分上下滑動改變亮度右半部分上下滑動改變聲音
這篇文章主要介紹了Android中 視頻屏幕左半部分上下滑動改變亮度右半部分上下滑動改變聲音的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-09-09
Android Studio通過Artifactory搭建本地倉庫優(yōu)化編譯速度的方法
這篇文章主要介紹了Android Studio通過Artifactory搭建本地倉庫優(yōu)化編譯速度的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03
Android實現(xiàn)使用微信登錄第三方APP的方法
這篇文章主要介紹了Android實現(xiàn)使用微信登錄第三方APP的方法,結(jié)合實例形式分析了Android微信登錄APP的操作步驟與具體功能實現(xiàn)技巧,需要的朋友可以參考下2016-11-11

