1. 什么是內(nèi)存泄漏嘲碱?
JVM內(nèi)存管理
關(guān)于內(nèi)存泄漏我們要知道造成,JVM內(nèi)存分配的幾種策略趁餐。
- 靜態(tài)的
靜態(tài)的存儲(chǔ)區(qū),內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好了菇绵,這塊內(nèi)存在程序整個(gè)運(yùn)行期間都一直存在肄渗,它主要存放靜態(tài)數(shù)據(jù)、全局的static數(shù)據(jù)和一些常量咬最。
2.棧式的
在執(zhí)行方法時(shí)翎嫡,方法一些內(nèi)部變量的存儲(chǔ)都可以放在棧上面創(chuàng)建,方法執(zhí)行結(jié)束的時(shí)候這些存儲(chǔ)單元就會(huì)自動(dòng)被注釋掉永乌。棧 內(nèi)存包括分配的運(yùn)算速度很快惑申,因?yàn)閮?nèi)在在處理器里面。當(dāng)然容量有限铆遭,并且棧式一塊連續(xù)的內(nèi)存區(qū)域硝桩,大小是由操作系統(tǒng)決定的,他先進(jìn)后 出枚荣,進(jìn)出完成不會(huì)產(chǎn)生碎片碗脊,運(yùn)行效率高且穩(wěn)定
3.堆式的
也叫動(dòng)態(tài)內(nèi)存 。我們通常使用new 來申請(qǐng)分配一個(gè)內(nèi)存橄妆。這里也是我們討論內(nèi)存泄漏優(yōu)化的關(guān)鍵存儲(chǔ)區(qū)衙伶。GC會(huì)根據(jù)內(nèi)存的使用情況,對(duì)堆內(nèi)存里的垃圾內(nèi)存進(jìn)行回收害碾。
堆內(nèi)存是一塊不連續(xù)的內(nèi)存區(qū)域矢劲,如果頻繁地new/remove會(huì)造成大量的內(nèi)存碎片,GC頻繁的回收慌随,導(dǎo)致內(nèi)存抖動(dòng)芬沉,這也會(huì)消耗我們應(yīng)用的性能
我們知道可以調(diào)用 System.gc();進(jìn)行內(nèi)存回收,但是GC不一定會(huì)執(zhí)行阁猜。
面對(duì)GC的機(jī)制丸逸,我們是否無能為力?其實(shí)我們可以通過聲明一些引用標(biāo)記來讓GC更好對(duì)內(nèi)存進(jìn)行回收剃袍。
類型 | 回收時(shí)機(jī) | 生命周期 |
---|---|---|
StrongReference (強(qiáng)引用) | 任何時(shí)候GC是不能回收他的黄刚,哪怕內(nèi)存不足時(shí),系統(tǒng)會(huì)直接拋出異常OutOfMemoryError民效,也不會(huì)去回收 | 進(jìn)程終止 |
SoftReference (軟引用) | 當(dāng)內(nèi)存足夠時(shí)不會(huì)回收這種引用類型的對(duì)象憔维,只有當(dāng)內(nèi)存不夠用時(shí)才會(huì)回收 | 內(nèi)存不足涛救,進(jìn)行GC的時(shí)候 |
WeakReference (弱引用) | GC一運(yùn)行就會(huì)把給回收了 | GC后終止 |
PhantomReference (虛引用) | 如果一個(gè)對(duì)象與虛引用關(guān)聯(lián),則跟沒有引用與之關(guān)聯(lián)一樣业扒,在任何時(shí)候都可能被垃圾回收器回收 | 任何時(shí)候都有可能 |
開發(fā)時(shí)检吆,為了防止內(nèi)存溢出,處理一些比較占用內(nèi)存并且生命周期長(zhǎng)的對(duì)象時(shí)凶赁,可以盡量使用軟引用和弱引用咧栗。
Tip:成員變量全部存儲(chǔ)在堆中(包括基本數(shù)據(jù)類型,引用及引用的對(duì)象實(shí)體)虱肄,因?yàn)樗麄儗儆陬愔掳澹悓?duì)象最終還是要被new出來的。
局部變量的基本數(shù)據(jù)類型和引用存在棧中咏窿,應(yīng)用的對(duì)象實(shí)體存儲(chǔ)在堆中斟或。因?yàn)樗鼈儗儆诜椒ó?dāng)中的變量,生命周期會(huì)隨著方法一起結(jié)束
內(nèi)存泄漏的定義
當(dāng)一個(gè)對(duì)象已經(jīng)不需要使用了集嵌,本該被回收時(shí)萝挤,而有另外一個(gè)正在使用的對(duì)象持有它的引用,從而導(dǎo)致了對(duì)象不能被GC回收根欧。
這種導(dǎo)致了本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中怜珍,就產(chǎn)生了內(nèi)存泄漏。
內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
- 內(nèi)存泄漏(Memory Leak)
進(jìn)程中某些對(duì)象已經(jīng)沒有使用的價(jià)值了凤粗,但是他們卻還可以直接或間接地被引用到GC Root導(dǎo)致無法回收酥泛。當(dāng)內(nèi)存泄漏過多的時(shí)候,再加上應(yīng)用本身占用的內(nèi)存嫌拣,日積月累最終就會(huì)導(dǎo)致內(nèi)存溢出OOM
- 內(nèi)存溢出(OOM)
當(dāng) 應(yīng)用的heap資源超過了Dalvik虛擬機(jī)分配的內(nèi)存就會(huì)內(nèi)存溢出
內(nèi)存泄漏帶來的影響
- 應(yīng)用卡頓
泄漏的內(nèi)存影響了GC的內(nèi)存分配柔袁,過多的內(nèi)存泄漏會(huì)影響應(yīng)用的執(zhí)行效率
- 應(yīng)用異常(OOM)
過多的內(nèi)存泄漏,最終會(huì)導(dǎo)致 Dalvik可分配的內(nèi)存越來越少异逐,更加容易出現(xiàn)OOM
2. Android開發(fā)常見的內(nèi)存泄漏
(1) 單例造成的內(nèi)存泄漏
錯(cuò)誤示例
當(dāng)調(diào)用getInstance時(shí)捶索,如果傳入的context是Activity的context。
只要這個(gè)單例沒有被釋放灰瞻,那么這個(gè)Activity也不會(huì)被釋放一直到進(jìn)程退出才會(huì)釋放腥例。
解決方案
能使用Application的Context就不要使用Activity的Content,Application的生命周期伴隨著整個(gè)進(jìn)程的周期
(2)非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏
錯(cuò)誤示例
解決方案
將非靜態(tài)內(nèi)部類修改為靜態(tài)內(nèi)部類酝润。(靜態(tài)內(nèi)部類不會(huì)隱式持有外部類)
(3)Handler造成的內(nèi)存泄漏
錯(cuò)誤示例
mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實(shí)例燎竖,所以它持有外部類Activity的引用,我們知道消息隊(duì)列是在一個(gè)Looper線程中不斷輪詢處理消息袍祖,那么當(dāng)這個(gè)Activity退出時(shí)消息隊(duì)列中還有未處理的消息或者正在處理消息底瓣,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用谢揪,mHandler又持有Activity的引用蕉陋,所以導(dǎo)致該Activity的內(nèi)存資源無法及時(shí)回收捐凭,引發(fā)內(nèi)存泄漏。
解決方案
創(chuàng)建一個(gè)靜態(tài)Handler內(nèi)部類凳鬓,然后對(duì)Handler持有的對(duì)象使用弱引用茁肠,這樣在回收時(shí)也可以回收Handler持有的對(duì)象,這樣雖然避免了Activity泄漏缩举,不過Looper線程的消息隊(duì)列中還是可能會(huì)有待處理的消息垦梆,所以我們?cè)贏ctivity的Destroy時(shí)或者Stop時(shí)應(yīng)該移除消息隊(duì)列中的消息
(4)線程造成的內(nèi)存泄漏
錯(cuò)誤示例
異步任務(wù)和Runnable都是一個(gè)匿名內(nèi)部類,因此它們對(duì)當(dāng)前Activity都有一個(gè)隱式引用仅孩。如果Activity在銷毀之前托猩,任務(wù)還未完成, 那么將導(dǎo)致Activity的內(nèi)存資源無法回收辽慕,造成內(nèi)存泄漏
解決方案
使用 靜態(tài)內(nèi)部類京腥,避免了Activity的內(nèi)存資源泄漏,當(dāng)然在Activity銷毀時(shí)候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask::cancel()溅蛉,避免任務(wù)在后臺(tái)執(zhí)行浪費(fèi)資源
(5)資源未關(guān)閉造成的內(nèi)存泄漏
錯(cuò)誤示例
對(duì)于使用了BraodcastReceiver公浪,ContentObserver,F(xiàn)ile船侧,Cursor欠气,Stream,Bitmap等資源的使用镜撩,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷预柒,否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏
解決方案
在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷
(6)使用了靜態(tài)的Activity和View
錯(cuò)誤示例
解決方案
應(yīng)該及時(shí)將靜態(tài)的應(yīng)用 置為null琐鲁,而且一般不建議將View及Activity設(shè)置為靜態(tài)
(7)注冊(cè)了系統(tǒng)的服務(wù)卫旱,但onDestory未注銷
錯(cuò)誤示例
解決方案
//不需要用的時(shí)候記得移除監(jiān)聽sensorManager.unregisterListener(listener);
(8)不需要用的監(jiān)聽未移除會(huì)發(fā)生內(nèi)存泄露
錯(cuò)誤示例
解決方案
Tip:tv.setOnClickListener();//監(jiān)聽執(zhí)行完回收對(duì)象,不用考慮內(nèi)存泄漏
tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監(jiān)聽围段,放到集合里面顾翼,需要考慮內(nèi)存泄漏