android 內(nèi)存泄漏全面解析

引言:

C/C++ 自己去分配內(nèi)存和釋放內(nèi)存--手動(dòng)管理

malloc free

什么是內(nèi)存泄露:內(nèi)存不在GC掌控之內(nèi)了趟卸。

當(dāng)一個(gè)對(duì)象已經(jīng)不需要再使用了蹄葱,本該被回收時(shí),而有另外一個(gè)正在使用的對(duì)象持有它的引用從而就導(dǎo)致

對(duì)象不能被回收锄列。這種導(dǎo)致了本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中图云,就產(chǎn)生了內(nèi)存泄漏

了解java的GC內(nèi)存回收機(jī)制:某對(duì)象不再有任何的引用的時(shí)候才會(huì)進(jìn)行回收。

ArrayList<String> list = new Arraylist<String>();

了解內(nèi)存分配的幾種策略:

1.靜態(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ù)(方法)時(shí)帕翻,函數(shù)一些內(nèi)部變量的存儲(chǔ)都可以放在棧上面創(chuàng)建,函數(shù)執(zhí)行結(jié)束的時(shí)候這些存儲(chǔ)單元就會(huì)自動(dòng)被釋放掉萝风。

棧內(nèi)存包括分配的運(yùn)算速度很快嘀掸,因?yàn)閮?nèi)置在處理器的里面的。當(dāng)然容量有限规惰。

3.堆式的

也叫做動(dòng)態(tài)內(nèi)存分配睬塌。有時(shí)候可以用malloc或者new來(lái)申請(qǐng)分配一個(gè)內(nèi)存。在C/C++可能需要自己負(fù)責(zé)釋放(java里面直接依賴GC機(jī)制)。

在C/C++這里是可以自己掌控內(nèi)存的揩晴,需要有很高的素養(yǎng)來(lái)解決內(nèi)存的問(wèn)題勋陪。java在這一塊貌似程序員沒(méi)有很好的方法自己去解決垃圾內(nèi)存,需要的是編程的時(shí)候就要注意自己良好的編程習(xí)慣硫兰。

區(qū)別:堆是不連續(xù)的內(nèi)存區(qū)域诅愚,堆空間比較靈活也特別大。

棧式一塊連續(xù)的內(nèi)存區(qū)域劫映,大小是有操作系統(tǒng)覺(jué)決定的违孝。

堆管理很麻煩,頻繁地new/remove會(huì)造成大量的內(nèi)存碎片泳赋,這樣就會(huì)慢慢導(dǎo)致效率低下雌桑。

對(duì)于棧的話,他先進(jìn)后出祖今,進(jìn)出完全不會(huì)產(chǎn)生碎片校坑,運(yùn)行效率高且穩(wěn)定。

public class Main{

int a = 1;

Student s = new Student();

public void XXX(){

int b = 1;//棧里面

Student s2 = new Student();
}
}

1.成員變量全部存儲(chǔ)在堆中(包括基本數(shù)據(jù)類型千诬,引用及引用的對(duì)象實(shí)體)---因?yàn)樗麄儗儆陬愃D浚悓?duì)象最終還是要被new出來(lái)的。

2.局部變量的基本數(shù)據(jù)類型和引用存儲(chǔ)于棧當(dāng)中大渤,引用的對(duì)象實(shí)體存儲(chǔ)在堆中制妄。-----因?yàn)樗麄儗儆诜椒ó?dāng)中的變量,生命周期會(huì)隨著方法一起結(jié)束泵三。

我們所討論內(nèi)存泄露耕捞,主要討論堆內(nèi)存,他存放的就是引用指向的對(duì)象實(shí)體烫幕。

有時(shí)候確實(shí)會(huì)有一種情況:當(dāng)需要的時(shí)候可以訪問(wèn)俺抽,當(dāng)不需要的時(shí)候可以被回收也可以被暫時(shí)保存以備重復(fù)使用。

比如:ListView或者GridView较曼、REcyclerView加載大量數(shù)據(jù)或者圖片的時(shí)候磷斧,

圖片非常占用內(nèi)存,一定要管理好內(nèi)存捷犹,不然很容易內(nèi)存溢出弛饭。

滑出去的圖片就回收,節(jié)省內(nèi)存萍歉÷滤蹋看ListView的源碼----回收對(duì)象,還會(huì)重用ConvertView枪孩。

如果用戶反復(fù)滑動(dòng)或者下面還有同樣的圖片憔晒,就會(huì)造成多次重復(fù)IO(很耗時(shí))藻肄,

那么需要緩存---平衡好內(nèi)存大小和IO,算法和一些特殊的java類拒担。

算法:lrucache(最近最少使用先回收)

特殊的java類:

利于回收嘹屯,StrongReference,SoftReference从撼,WeakReference州弟,PhatomReference

StrongReference強(qiáng)引用:

回收時(shí)機(jī):從不回收 使用:對(duì)象的一般保存 生命周期:JVM停止的時(shí)候才會(huì)終止

SoftReference,軟引用

回收時(shí)機(jī):當(dāng)內(nèi)存不足的時(shí)候低零;使用:SoftReference<String>結(jié)合ReferenceQueue構(gòu)造有效期短呆馁;生命周期:內(nèi)存不足時(shí)終止

WeakReference,弱引用

回收時(shí)機(jī):在垃圾回收的時(shí)候毁兆;使用:同軟引用; 生命周期:GC后終止

PhatomReference 虛引用

回收時(shí)機(jī):在垃圾回收的時(shí)候阴挣;使用:合ReferenceQueue來(lái)跟蹤對(duì)象唄垃圾回收期回收的活動(dòng)气堕; 生命周期:GC后終止

開(kāi)發(fā)時(shí),為了防止內(nèi)存溢出畔咧,處理一些比較占用內(nèi)存大并且生命周期長(zhǎng)的對(duì)象的時(shí)候茎芭,可以盡量使用軟引用和弱引用。

軟引用比LRU算法更加任性誓沸,回收量是比較大的梅桩,你無(wú)法控制回收哪些對(duì)象。

比如使用場(chǎng)景:默認(rèn)頭像拜隧、默認(rèn)圖標(biāo)宿百。

ListView或者GridView、REcyclerView要使用內(nèi)存緩存+外部緩存(SD卡)

常見(jiàn)內(nèi)存泄露

1.單例模式導(dǎo)致內(nèi)存對(duì)象無(wú)法釋放而導(dǎo)致內(nèi)存泄露

能用Application的context就用Application的

2.設(shè)置監(jiān)聽(tīng)很容易出現(xiàn)內(nèi)存泄露
例如:

handler.post(callback)
onDestroy(){
    handler.removeCallback();
}

3.非靜態(tài)內(nèi)部類引起內(nèi)存泄露

public void loadData(){//隱士持有MainActivity實(shí)例洪添。MainActivity.this.a
        new Thread(new Runnable() {
            @Override
            public void run() {
            while(true){
                try {
                //int b=a;
                Thread.sleep(1000);
                } catch (InterruptedException e) {
                e.printStackTrace();
                }
            }
            }
        }).start();
        }

解決方案:
將非靜態(tài)內(nèi)部類修改為靜態(tài)內(nèi)部類垦页。
(靜態(tài)內(nèi)部類不會(huì)隱士持有外部類)

4.資源未關(guān)閉引起的內(nèi)存泄露情況

比如:BroadCastReceiver、Cursor干奢、Bitmap痊焊、IO流、自定義屬性attribute
attr.recycle()回收忿峻。
當(dāng)不需要使用的時(shí)候薄啥,要記得及時(shí)釋放資源。否則就會(huì)內(nèi)存泄露逛尚。

5.無(wú)限循環(huán)動(dòng)畫
沒(méi)有在onDestroy中停止動(dòng)畫垄惧,否則Activity就會(huì)變成泄露對(duì)象。
比如:輪播圖效果黑低。

如何找到項(xiàng)目中存在的內(nèi)存泄露的這些地方呢

1.確定是否存在內(nèi)存泄露
1)Android Monitors的內(nèi)存分析
最直觀的看內(nèi)存增長(zhǎng)情況赘艳,知道該動(dòng)作是否發(fā)生內(nèi)存泄露酌毡。
動(dòng)作發(fā)生之前:GC完后內(nèi)存1.4M; 動(dòng)作發(fā)生之后:GC完后內(nèi)存1.6M

2)使用MAT內(nèi)存分析工具
MAT分析heap的總內(nèi)存占用大小來(lái)初步判斷是否存在泄露
Heap視圖中有一個(gè)Type叫做data object,即數(shù)據(jù)對(duì)象蕾管,也就是我們的程序中大量存在的類類型的對(duì)象枷踏。
在data object一行中有一列是“Total Size”,其值就是當(dāng)前進(jìn)程中所有Java數(shù)據(jù)對(duì)象的內(nèi)存總量掰曾,
一般情況下旭蠕,這個(gè)值的大小決定了是否會(huì)有內(nèi)存泄漏。
我們反復(fù)執(zhí)行某一個(gè)操作并同時(shí)執(zhí)行GC排除可以回收掉的內(nèi)存旷坦,注意觀察data object的Total Size值掏熬,
正常情況下Total Size值都會(huì)穩(wěn)定在一個(gè)有限的范圍內(nèi),也就是說(shuō)由于程序中的的代碼良好秒梅,沒(méi)有造成對(duì)象不被垃圾回收的情況旗芬。
反之如果代碼中存在沒(méi)有釋放對(duì)象引用的情況,隨著操作次數(shù)的增多Total Size的值會(huì)越來(lái)越大捆蜀。
那么這里就已經(jīng)初步判斷這個(gè)操作導(dǎo)致了內(nèi)存泄露的情況疮丛。

2.先找懷疑對(duì)象(哪些對(duì)象屬于泄露的)
MAT對(duì)比操作前后的hprof來(lái)定位內(nèi)存泄露是泄露了什么數(shù)據(jù)對(duì)象。(這樣做可以排除一些對(duì)象辆它,不用后面去查看所有被引用的對(duì)象是否是嫌疑)
快速定位到操作前后所持有的對(duì)象哪些是增加了(GC后還是比之前多出來(lái)的對(duì)象就可能是泄露對(duì)象嫌疑犯)
技巧:Histogram中還可以對(duì)對(duì)象進(jìn)行Group誊薄,比如選擇Group By Package更方便查看自己Package中的對(duì)象信息。

  1. MAT分析hprof來(lái)定位內(nèi)存泄露的原因所在锰茉。(哪個(gè)對(duì)象持有了上面懷疑出來(lái)的發(fā)生泄露的對(duì)象)
    1)Dump出內(nèi)存泄露“當(dāng)時(shí)”的內(nèi)存鏡像hprof呢蔫,分析懷疑泄露的類;
    2)把上面2得出的這些嫌疑犯一個(gè)一個(gè)排查個(gè)遍飒筑。步驟:
    (1)進(jìn)入Histogram片吊,過(guò)濾出某一個(gè)嫌疑對(duì)象類
    (2)然后分析持有此類對(duì)象引用的外部對(duì)象(在該類上面點(diǎn)擊右鍵List Objects--->with incoming references)
    (3)再過(guò)濾掉一些弱引用、軟引用协屡、虛引用定鸟,因?yàn)樗鼈冞t早可以被GC干掉不屬于內(nèi)存泄露
    (在類上面點(diǎn)擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
    (4)逐個(gè)分析每個(gè)對(duì)象的GC路徑是否正常
    此時(shí)就要進(jìn)入代碼分析此時(shí)這個(gè)對(duì)象的引用持有是否合理,這就要考經(jīng)驗(yàn)和體力了著瓶!
    (比如上課的例子中:旋轉(zhuǎn)屏幕后MainActivity有兩個(gè)联予,肯定MainActivity發(fā)生泄露了,
    那誰(shuí)導(dǎo)致他泄露的呢材原?原來(lái)是我們的CommonUtils類持有了旋轉(zhuǎn)之前的那個(gè)MainActivity他沸久,
    那是否合理?結(jié)合邏輯判斷當(dāng)然不合理余蟹,由此找到內(nèi)存泄露根源是CommonUtils類持有了該MainActivity實(shí)例造成的卷胯。
    怎么解決?罪魁禍?zhǔn)渍业搅送疲趺唇鉀Q應(yīng)該不難了窑睁,不同情況解決辦法不一樣挺峡,要靠你的智慧了。)

context.getapplictioncontext()可以嗎担钮?
可以3髟!只要讓CommonUtils類不直接只有MainActivity的實(shí)例就可以了箫津。

一般我是最笨的方法解決
new出來(lái)對(duì)象狭姨,用完后把它 = null;這樣算不算優(yōu)化
假如:方法里面定義的對(duì)象,要去管嗎苏遥?一般不需要管饼拍。
自己=null,要自己去控制所有對(duì)象的生命周期 判斷各種空指針田炭,有點(diǎn)麻煩师抄。
但是在很多時(shí)候去想到主動(dòng)將對(duì)象置為null是很好的習(xí)慣。

判斷一個(gè)應(yīng)用里面內(nèi)存泄露避免得很好教硫,怎么看司澎?
當(dāng)app退出的時(shí)候,這個(gè)進(jìn)程里面所有的對(duì)象應(yīng)該就都被回收了栋豫,尤其是很容易被泄露的(View,Activity)是否還內(nèi)存當(dāng)中谚殊。
可以讓app退出以后丧鸯,查看系統(tǒng)該進(jìn)程里面的所有的View、Activity對(duì)象是否為0.
工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的數(shù)量是否為0.

常用優(yōu)化工具

1.Profiler

2.LeakCanary

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嫩絮,一起剝皮案震驚了整個(gè)濱河市丛肢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剿干,老刑警劉巖蜂怎,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異置尔,居然都是意外死亡杠步,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門榜轿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)幽歼,“玉大人,你說(shuō)我怎么就攤上這事谬盐〉樗剑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵飞傀,是天一觀的道長(zhǎng)皇型。 經(jīng)常有香客問(wèn)我诬烹,道長(zhǎng),這世上最難降的妖魔是什么弃鸦? 我笑而不...
    開(kāi)封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任绞吁,我火速辦了婚禮,結(jié)果婚禮上寡键,老公的妹妹穿的比我還像新娘掀泳。我一直安慰自己,他們只是感情好西轩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布员舵。 她就那樣靜靜地躺著,像睡著了一般藕畔。 火紅的嫁衣襯著肌膚如雪马僻。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天注服,我揣著相機(jī)與錄音韭邓,去河邊找鬼。 笑死溶弟,一個(gè)胖子當(dāng)著我的面吹牛女淑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辜御,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鸭你,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了擒权?” 一聲冷哼從身側(cè)響起袱巨,我...
    開(kāi)封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碳抄,沒(méi)想到半個(gè)月后愉老,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剖效,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年嫉入,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片璧尸。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡劝贸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逗宁,到底是詐尸還是另有隱情映九,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布瞎颗,位于F島的核電站件甥,受9級(jí)特大地震影響捌议,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜引有,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一瓣颅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧譬正,春花似錦宫补、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至抒巢,卻和暖如春贫贝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛉谜。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工稚晚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人型诚。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓客燕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親狰贯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子也搓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 內(nèi)存管理的目的 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。簡(jiǎn)單粗...
    晨光光閱讀 1,294評(píng)論 1 4
  • 在 Java 中暮现,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放則是由 Garbage Collecation(GC) 完...
    Shawn_Dut閱讀 5,889評(píng)論 3 28
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題楚昭。內(nèi)存泄漏...
    _痞子閱讀 1,637評(píng)論 0 8
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題栖袋。內(nèi)存泄漏...
    apkcore閱讀 1,221評(píng)論 2 7
  • 內(nèi)存管理的目的就是讓我們?cè)陂_(kāi)發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問(wèn)題。內(nèi)存泄漏大家都不陌生了抚太,簡(jiǎn)單粗俗的講塘幅,...
    宇宙只有巴掌大閱讀 2,363評(píng)論 0 12