一文搞懂為啥你的應(yīng)用內(nèi)存泄漏?
來源:原創(chuàng) 時間:2017-10-20 瀏覽:0 次前語
最近在項目中偶然會發(fā)現(xiàn)內(nèi)存走漏現(xiàn)象。一開端仍是一臉懵逼的查來查去:這怎樣就走漏了?這樣居然沒走漏?一向沒有個明晰地思路。這幾天閑下來,計劃仔細(xì)收拾學(xué)習(xí)一下。我在這兒從一個“怎樣主動形成內(nèi)存走漏”的視點來學(xué)習(xí),然后了解一下不同辦法檢測的成果怎樣,這樣今后再遇到相關(guān)問題時就能夠很快的處理了。
java gc
首要要有一個大前提,也就是java gc。在大部分虛擬機(jī)(包含Android的ART)中,Java都采用了“可達(dá)性剖析”算法來進(jìn)行內(nèi)存收回,原理是:會有幾個引證作為root節(jié)點,關(guān)于恣意目標(biāo)來說,如果從root層層遍歷,如果找不到關(guān)于他的引證鏈,那么這個目標(biāo)就被標(biāo)記為無用,就會在gc時被毀掉。
為何走漏?
內(nèi)存走漏,即部分目標(biāo)盡管現(xiàn)已不再運用,可是由于有root持有引證,所以并沒有被毀掉,所占用的內(nèi)存一向沒有被開釋。一次兩次發(fā)作影響不大。如果頻頻發(fā)作,那么可用內(nèi)存會逐漸缺乏,終究在某一次懇求內(nèi)存時發(fā)現(xiàn)內(nèi)存缺乏而發(fā)作oom。這兒要清晰一個概念,只要強(qiáng)引證會發(fā)作內(nèi)存走漏,而weak等引證由于其特別機(jī)制,所以影響不大。
走漏影響比較大的就是一些大目標(biāo),常見的比方某些資源,bitmap,以及activity。
怎樣發(fā)作走漏
首要讓我們從另一個視點來看,怎樣主動發(fā)作內(nèi)存走漏呢?當(dāng)然是想辦法給他一個一向存在的強(qiáng)引證了。
static
static這個關(guān)鍵字使一個變量變?yōu)橹缓瓦@個類相關(guān)的類變量,和實例無關(guān)。他的生命周期是很長的,貫穿于app的啟動到封閉。因而只要用一個static引證一個大目標(biāo),就能夠走漏了!舉個比方:
static Activity activity;
這是最簡略粗獷的持有一個activity的引證,這樣這個activity退出之后目標(biāo)并沒有被毀掉。
static View view;
一個View初始化時會用到context,我們在自定義View,重寫結(jié)構(gòu)辦法時就知道這個了。因而如果一個View也像這樣被持有,那個context也不會被開釋。
innerClass
內(nèi)部類有個特性,是他會持有一個外部類的引證。如果內(nèi)部類的實例一向存活,那么外部類activity的實例也就一向在。比方持有一個static的內(nèi)部類引證:
或許曾經(jīng)我們用asynctask時喜愛搞一個匿名內(nèi)部類履行異步使命,那當(dāng)我們activity退出后這個異步使命還在履行的話,就會走漏了。
還有自己開個匿名線程:
還有在運用handler時,如果用了匿名handler,那么這個handler會帶著activity的引證藏到音訊行列中。音訊沒有被處理,就會形成內(nèi)存走漏。相似的,還有timertask等。
register
我們平常會用到許多第三方庫,比方ButterKnife EventBus RxJava等等,有的時分要獲取體系效勞,getSystemService。在運用的時分,都有一個先registerd或許bind的操作,并且在創(chuàng)立的時分會把activity的引證傳過去。如果在activity完畢時沒有unregister或許unbind,就會形成內(nèi)存走漏。
怎樣檢測走漏
最簡略的辦法天然就是運用leakcanary了。只要給自己的項目加上這個東西,在發(fā)作走漏的時分很快就會有提示。
除此之外,android studio的刀耕火種的方法也不錯,在這兒我拿一個比方來演示一下我是怎樣用的。
準(zhǔn)備工作
首要,我寫了兩個activity,一個MainActivity,一個MemoryLeakActivity,邏輯是:MainActivity中有個按鈕,點擊會調(diào)到MemoryLeakActivity,在這個activity中會成心發(fā)作內(nèi)存走漏,代碼如下:
在開端之前,再了解一下這個
這個Monitors能夠調(diào)查當(dāng)時選中app的運轉(zhuǎn)情況,現(xiàn)在只需求重視我標(biāo)了123的當(dāng)?shù)亍?/span>
首要這個Memory就是當(dāng)時app的內(nèi)存運用情況:
發(fā)作一個當(dāng)時java堆的.hprof文件,這個文件反映了當(dāng)時時間java堆中內(nèi)存概況,記住這個玩意有大用!
手動進(jìn)行一次gc
這一塊很重要,首要他有兩個部分,藍(lán)色和灰色。藍(lán)色部分是當(dāng)時內(nèi)存運用巨細(xì),灰色部分是這個app被約束的最大內(nèi)存巨細(xì)。當(dāng)藍(lán)色部分越來越大,最終和灰色部分一樣時,闡明我們內(nèi)存運用許多了行將內(nèi)存缺乏,此刻會進(jìn)行一次gc一起將回灰色部分即約束的巨細(xì)進(jìn)步。
肉眼調(diào)查
好了,介紹完這個東西,我們開端著手實踐。首要翻開app,點擊按鈕跳到會發(fā)作走漏的activity上,再按回來鍵,然后再次按下按鈕……這樣重復(fù)操作:
與此一起,調(diào)查monitors的memory窗口,會發(fā)現(xiàn)藍(lán)色部分在每一次敞開新activity時會增加一部分,這很正常??墒窃诨貋頃r,分明activity被“退出”了,可是藍(lán)色部分仍是沒有改變。重復(fù)幾回之后,藍(lán)色部分一向在增加。也就是說當(dāng)時內(nèi)存越用越多,能夠揣度現(xiàn)已發(fā)作內(nèi)存走漏啦~
主動剖析
接下因由android studio來剖析一下。在重復(fù)幾回上面的操作之后,回來MainActivity,然后點擊dump java heap按鈕,然后等一會兒,android studio在為我們dump此刻的horof文件。在成功后,會主動翻開:
如圖在這個界面中,我們看最右面有一個欄叫 Analyzer Tasks,翻開它,會發(fā)現(xiàn)有兩個選項。我們是來看activity的內(nèi)存走漏的,那就把那個查重復(fù)字符串的√去掉。然后點右邊那個綠色小三角,會發(fā)現(xiàn)下面Analysis Results欄里邊展現(xiàn)出了當(dāng)時走漏的Activity引證:
點擊第一個item,最下方Reference Tree欄中便展現(xiàn)出了詳細(xì)的引證:
一般來說,第一個就是我們發(fā)作走漏的當(dāng)?shù)?。在圖中,this$0的意思是隱式的引證。也就是說,我們的activity是由于一個內(nèi)部類而發(fā)作了內(nèi)存走漏。
再點擊方才results中第二個item,看一下下方的reference tree:
能夠看到顯式的有一個leakCntextRef引證,這闡明我們有一個名為leakCntextRef的引證持有了activity?;剡^頭看看我們的代碼,公然,驗證的沒錯。
拓寬
android studio的剖析還算比較簡略并且內(nèi)容較少,我們能夠把這個hprof導(dǎo)出,然后用mat來剖析。
怎樣處理走漏
已然發(fā)作了走漏,那就要處理它,防止問題呈現(xiàn)。那么怎樣處理呢?很簡略,走漏是由于持有了activity引證導(dǎo)致無法被毀掉,那么只要兩個挑選:及時撤銷引證,或許讓這個引證多待一會,可是該gc的時分就毀掉。
依據(jù)這個思路:
我們在代碼中能不必static變量持有contxt就不必,非要用就用weak引證。
關(guān)于內(nèi)部類,盡量用靜態(tài)內(nèi)部類,這樣就不會持有外部類引證。如果需求外部類引證做一些事,就手動賦給一個weak引證。
關(guān)于匿名內(nèi)部類,不要圖簡略便利,真實不可就乖乖的寫成外部類。
異步操作,盡量用能夠便利辦理的,比方rxJava,而不是用老古董AsyncTask了。非要用也最好加一個停止條件,在退出Activity時就該完畢了。
在用rx時,能夠在subscribe()的時分獲取到Subscripeion,在不必的時分手動unSubscribe(),或許直接bind()到Activity的生命周期上,比方運用RxActivity辦理。
在運用handler時,記住在activity的onDestroy()中加上remove()
在獲取到某些資源時,運用完記住開釋
在用到一些大目標(biāo)比方Bitmap啊什么的,要記住收回
最終,在運用各種第三方庫或許體系效勞的時分還要記住有注冊或綁定就要有免除注冊、解綁定。