欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android內(nèi)存泄漏導(dǎo)致原因深入探究

 更新時(shí)間:2023年02月17日 17:03:41   作者:守住Android最后的光  
內(nèi)存管理的目的就是讓我們?cè)陂_發(fā)過程中有效避免我們的應(yīng)用程序出現(xiàn)內(nèi)存泄露的問題。內(nèi)存泄露相信大家都不陌生,我們可以這樣理解:沒有用的對(duì)象無法回收的現(xiàn)象就是內(nèi)存泄露

什么是內(nèi)存泄露

什么是內(nèi)存泄露,通俗的來說就是堆中的一些對(duì)象已經(jīng)不會(huì)再被使用了,但垃圾收集器卻無法將它們從內(nèi)存中清除。

內(nèi)存泄漏很嚴(yán)重的問題,因?yàn)樗鼤?huì)阻塞內(nèi)存資源并隨著時(shí)間的推移降低系統(tǒng)性能。如果不進(jìn)行有效的處理,最終的結(jié)果將會(huì)使應(yīng)用程序耗盡內(nèi)存資源,無法正常服務(wù),導(dǎo)致程序崩潰,拋出java.lang.OutOfMemoryError異常。

堆內(nèi)存中通常有兩種類型的對(duì)象:被引用的對(duì)象和未被引用的對(duì)象。被引用的對(duì)象是應(yīng)用程序中仍然具有活躍的引用,而未被引用的對(duì)象則沒有任何活躍的引用。

垃圾收集器會(huì)回收那些未被引用的對(duì)象,但不會(huì)回收那些還在被引用的對(duì)象。這也是內(nèi)存泄露發(fā)生的源頭。

哪些操作會(huì)造成內(nèi)存泄漏

下面我們介紹幾種常見的造成內(nèi)存泄露的情況

1、意外聲明全局變量是最常見也最容易修復(fù)的內(nèi)存泄漏問題,比如:

function fn() {
    name = '張三';
}

解釋器在解釋上面的函數(shù)時(shí),會(huì)把name當(dāng)做全局變量,即window.name = ‘張三’。只要window對(duì)象沒有被清理,那么name屬性和屬性值將一直存在,造成內(nèi)存泄露。

解決方法:

(1)只要在變量聲明前面加上var、let或const關(guān)鍵字即可,這樣變量就會(huì)在函數(shù)執(zhí)行完畢后離開作用域。

(2)使用this關(guān)鍵字

function fn() {
    this.name = '張三';
}

(3)可以在 JavaScript 文件開頭添加 “use strict”,使用嚴(yán)格模式。這樣在嚴(yán)格模式下解析 JavaScript 可以防止意外的全局變量

(4)在使用完之后,對(duì)其賦值為null或者重新分配

2、 定時(shí)器導(dǎo)致的泄露

let name = '張三';
setInterval(() => {
    console.log(name);
}, 100);

上面的代碼中,只要定時(shí)器一直運(yùn)行,回調(diào)函數(shù)中引用的name就會(huì)一直占用內(nèi)存。

3、閉包、控制臺(tái)日志、循環(huán)(在兩個(gè)對(duì)象彼此引用且彼此保留時(shí),就會(huì)產(chǎn)生一個(gè)循環(huán)),下面我們看一個(gè)JavaScript閉包導(dǎo)致的內(nèi)訓(xùn)泄露例子

let fun = function() {
    let name = '張三';
    return function() {
        return name;
    };
};

調(diào)用fun()會(huì)導(dǎo)致分配給name的內(nèi)存被泄漏。以上代碼執(zhí)行后創(chuàng)建了一個(gè)內(nèi)部閉包,只要返回的函數(shù)存在就不能清理name,因?yàn)殚]包一直在引用著它。

常見內(nèi)存泄露問題

1.資源性對(duì)象未關(guān)閉

資源性對(duì)象(如Cursor、File等一些Closeable對(duì)象),它們往往使用了緩沖區(qū),緩沖區(qū)不僅在JVM內(nèi),JVM之外也有。如果僅僅把變量設(shè)置為null,而不關(guān)閉它們,緩沖區(qū)得不到釋放,往往造成內(nèi)存泄露。

解決方案:一般在finally中關(guān)閉資源型對(duì)象,而后設(shè)置對(duì)象為null

2.注冊(cè)對(duì)象未注銷

訂閱者模式中,如果注冊(cè)對(duì)象不再使用時(shí),未及時(shí)注銷,會(huì)導(dǎo)致訂閱者列表中維持這對(duì)象的引用,阻止垃圾回收,導(dǎo)致內(nèi)存泄露。常見場景:動(dòng)態(tài)注冊(cè)BroadcastReceiver,注冊(cè)PhoneStateListener,注冊(cè)EventBus等等,

還有自定義使用訂閱者模式的情形。

解決方案:一般在onDestroy()中進(jìn)行解注冊(cè)

3.非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例

非靜態(tài)內(nèi)部類持有外部類實(shí)例的引用,若非靜態(tài)內(nèi)部類的實(shí)例是靜態(tài)的,便擁有app存活期整個(gè)生命周期,長期持有外部類的引用,阻止外部類實(shí)例被回收。

使用內(nèi)部類的情況十分常見,尤其是匿名內(nèi)部類:一些接口的匿名實(shí)現(xiàn)類,都是內(nèi)部類。

解決方案:(1)改為靜態(tài)內(nèi)部類,不再持有外部類實(shí)例的引用 (2)避免申明非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例 (3)將內(nèi)部類抽取出來封裝成一個(gè)單例,如果需要Context,沒有特殊要求就使用Application Context;如果需要Activity Context,則使用完畢置空,或者使用弱引用

4.單例模式引起的內(nèi)存泄露

由于單例模式的靜態(tài)特性,使得它的生命周期和我們的應(yīng)用一樣長,如果讓單例無限制的持有Activity的強(qiáng)引用就會(huì)導(dǎo)致內(nèi)存泄漏

解決方案:使用Activity的弱引用,或者沒特殊需求時(shí)使用Application Context

5.Handler臨時(shí)性內(nèi)存泄露

非靜態(tài)Handler持有Activity或Service的引用,Message中的target指向Handler實(shí)例,所以當(dāng)Message在MessageQueue中排隊(duì),長時(shí)間未得到處理時(shí),Activity邊不會(huì)被回收,導(dǎo)致臨時(shí)性內(nèi)存泄露。

解決方案:(1)使用靜態(tài)Handler內(nèi)部類,然后對(duì)Handler持有的對(duì)象(Activity或Service)使用弱引用 (2)在onDestroy()中移除消息隊(duì)列中的消息 mHandler.removeCallbacksAndMessages(null)

類似的:AsyncTask內(nèi)部也是Handler機(jī)制,也存在同樣的臨時(shí)性內(nèi)存泄露風(fēng)險(xiǎn)

6.容器中對(duì)象未及時(shí)清理導(dǎo)致內(nèi)存泄露

容器類一般擁有較長的生命周期,若內(nèi)部不再使用的對(duì)象不及時(shí)清理,內(nèi)部對(duì)象邊一直被容器類引用。上述2中的訂閱者列表也屬于容器類這中情況。另外常見的容器類還有線程池、對(duì)象池、圖片緩存池等。線程池中的線程若存在ThreadLocal對(duì)象,因?yàn)榫€程對(duì)象一直被循環(huán)使用,ThreadLocal對(duì)象便會(huì)一直被引用,要注意對(duì)value對(duì)象的置空釋放。

7.靜態(tài)View導(dǎo)致內(nèi)存泄露

有時(shí),當(dāng)一個(gè)Activity經(jīng)常啟動(dòng),但是對(duì)應(yīng)的View讀取非常耗時(shí),我們可以通過靜態(tài)View變量來保持對(duì)該Activity的rootView引用。這樣就可以不用每次啟動(dòng)Activity都去讀取并渲染View了。這確實(shí)是一個(gè)提高Activity啟動(dòng)速度的好方法!但是要注意,一旦View attach到我們的Window上,就會(huì)持有一個(gè)Context(即Activity)的引用。而我們的View有事一個(gè)靜態(tài)變量,所以導(dǎo)致Activity不被回收。 解決辦法:在使用靜態(tài)View時(shí),需要確保在資源回收時(shí),將靜態(tài)View detach掉。

8.屬性動(dòng)畫未及時(shí)關(guān)閉導(dǎo)致內(nèi)存泄露

在使用ValueAnimator或者ObjectAnimator時(shí),如果沒有及時(shí)做cancel取消動(dòng)畫,就可能造成內(nèi)存泄露。 因?yàn)樵赾ancel方法里,最后調(diào)用了endAnimation(); ,在endAnimation里,有個(gè)AnimationHandler的單例,會(huì)持有屬性動(dòng)畫對(duì)象的引用

解決辦法:在在onDestory時(shí),調(diào)用動(dòng)畫的cancel方法

9.WebView內(nèi)存泄露

目前Android中WebView的實(shí)現(xiàn)存在很大的兼容性問題,Google支持各個(gè)ROM廠商自行定制自己的WebView實(shí)現(xiàn),各個(gè)ROM間差異較大,且大多都存在內(nèi)存泄露問題。除了調(diào)用其內(nèi)部的clearCache()、clearHistory()、removeAllViews()、freeMemory()、destroy()和置null以外,一般比較粗暴有效的解決方法是:將包含WebView的Activity放在一個(gè)單獨(dú)的進(jìn)程中,不需要時(shí)將進(jìn)程銷毀,從而釋放所有所占內(nèi)存。

10.其他的系統(tǒng)控件以及自定義View

在 Android Lollipop 之前使用 AlertDialog 可能會(huì)導(dǎo)致內(nèi)存泄漏

view中有線程或者動(dòng)畫 要及時(shí)停止。這是為了防止內(nèi)存泄漏,可以在onDetachedFromWindow方法中結(jié)束,這個(gè)方法回調(diào)的時(shí)機(jī)是 當(dāng)View的Activity退出或者當(dāng)前View被移除的時(shí)候 會(huì)調(diào)用 這時(shí)候是結(jié)束動(dòng)畫或者線程的好時(shí)機(jī) 另外還有一個(gè)對(duì)應(yīng)的方法 onAttachedToWindow 這個(gè)方法調(diào)用的時(shí)機(jī)是在包含View的Activity啟動(dòng)時(shí) 回調(diào) 回調(diào)在onDraw方法之前

11.其他常見的引起內(nèi)存泄漏原因

  • (1)構(gòu)造Adapter時(shí),沒有使用緩存的 contentView
  • (2)Bitmap在不使用的時(shí)候沒有使用recycle()釋放內(nèi)存
  • (3)警惕線程未終止造成的內(nèi)存泄露;譬如在Activity中關(guān)聯(lián)了一個(gè)生命周期超過Activity的Thread,在退出Activity時(shí)切記結(jié)束線程。一個(gè)典型的例子就是HandlerThread的run方法是一個(gè)死循環(huán),它不會(huì)自己結(jié)束,線程的生命周期超過了Activity生命周期,我們必須手動(dòng)在Activity的銷毀方法中調(diào)用thread.getLooper().quit();才不會(huì)泄露
  • (4)避免代碼設(shè)計(jì)模式的錯(cuò)誤造成內(nèi)存泄露;譬如循環(huán)引用,A持有B,B持有C,C持有A,這樣的設(shè)計(jì)誰都得不到釋放

文末

理解內(nèi)存泄漏的危害,我們舉個(gè)簡單的例子。有一個(gè)賓館,有100間房間,顧客每次都是在前臺(tái)進(jìn)行登記,然后拿到房間鑰匙。如果有些顧客不需要該房間了,也不歸還鑰匙,久而久之,前臺(tái)處可用房間越來越少,收入也越來越少,瀕臨倒閉。當(dāng)程序申請(qǐng)了內(nèi)存,而不進(jìn)行歸還,久而久之,可用內(nèi)存越來越少,OS就會(huì)進(jìn)行自我保護(hù),殺掉該進(jìn)程,這就是我們常說的OOM(out of memory)。

到此這篇關(guān)于Android內(nèi)存泄漏導(dǎo)致原因深入探究的文章就介紹到這了,更多相關(guān)Android內(nèi)存泄漏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論