內(nèi)存泄漏和內(nèi)存溢出的區(qū)別與解決方式

內(nèi)存泄漏(memory leak )

是指程序在申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的內(nèi)存空間就造成了內(nèi)存泄漏,一次內(nèi)存泄漏似乎不會(huì)有大的影響,但內(nèi)存泄漏堆積后的后果就是內(nèi)存溢出。

我們知道了內(nèi)存泄漏的原因而內(nèi)存溢出則有可能是因?yàn)槲覀兾覀兌啻蝺?nèi)存泄漏堆積后的后果則變成了內(nèi)存溢出

內(nèi)存溢出 (out of memory)

指程序申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存供申請(qǐng)者使用,或者說(shuō),給了你一塊存儲(chǔ)int類(lèi)型數(shù)據(jù)的存儲(chǔ)空間,但是你卻存儲(chǔ)long類(lèi)型的數(shù)據(jù),那么結(jié)果就是內(nèi)存不夠用,此時(shí)就會(huì)報(bào)錯(cuò)OOM,即所謂的內(nèi)存溢出,簡(jiǎn)單來(lái)說(shuō)就是自己所需要使用的空間比我們擁有的內(nèi)存大內(nèi)存不夠使用所造成的內(nèi)存溢出。

內(nèi)存泄漏的分類(lèi)(按發(fā)生方式來(lái)分類(lèi))

  1. 常發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼會(huì)被多次執(zhí)行到,每次被執(zhí)行的時(shí)候都會(huì)導(dǎo)致一塊內(nèi)存泄漏。
  2. 發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過(guò)程下才會(huì)發(fā)生。常發(fā)性和偶發(fā)性是相對(duì)的。對(duì)于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測(cè)試環(huán)境和測(cè)試方法對(duì)檢測(cè)內(nèi)存泄漏至關(guān)重要。
  3. 一次性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只會(huì)被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會(huì)有一塊僅且一塊內(nèi)存發(fā)生泄漏。比如,在類(lèi)的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒(méi)有釋放該內(nèi)存,所以內(nèi)存泄漏只會(huì)發(fā)生一次。
  4. 隱式內(nèi)存泄漏。程序在運(yùn)行過(guò)程中不停的分配內(nèi)存,但是直到結(jié)束的時(shí)候才釋放內(nèi)存。嚴(yán)格的說(shuō)這里并沒(méi)有發(fā)生內(nèi)存泄漏,因?yàn)樽罱K程序釋放了所有申請(qǐng)的內(nèi)存。但是對(duì)于一個(gè)服務(wù)器程序,需要運(yùn)行幾天,幾周甚至幾個(gè)月,不及時(shí)釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類(lèi)內(nèi)存泄漏為隱式內(nèi)存泄漏。

什么情況下會(huì)導(dǎo)致內(nèi)存泄露

什么時(shí)候會(huì)發(fā)生內(nèi)存泄露??jī)?nèi)存泄露的根本原因:長(zhǎng)生命周期的對(duì)象持有短生命周期的對(duì)象。短周期對(duì)象就無(wú)法及時(shí)釋放。

  1. 靜態(tài)集合類(lèi)引起內(nèi)存泄露
    主要是hashmap,Vector等,如果是靜態(tài)集合 這些集合沒(méi)有及時(shí)setnull的話,就會(huì)一直持有這些對(duì)象。
  2. remove 方法無(wú)法刪除set集 Objects.hash(firstName, lastName);
    經(jīng)過(guò)測(cè)試,hashcode修改后,就沒(méi)有辦法remove了。
  3. observer 我們?cè)谑褂帽O(jiān)聽(tīng)器的時(shí)候,往往是addxxxlistener,但是當(dāng)我們不需要的時(shí)候,忘記removexxxlistener,就容易內(nèi)存leak。
    廣播沒(méi)有unregisterrecevier
  4. 各種數(shù)據(jù)鏈接沒(méi)有關(guān)閉,數(shù)據(jù)庫(kù)contentprovider,io,sokect等。cursor
  5. 內(nèi)部類(lèi):
    java中的內(nèi)部類(lèi)(匿名內(nèi)部類(lèi)),會(huì)持有宿主類(lèi)的強(qiáng)引用this。
    所以如果是new Thread這種,后臺(tái)線程的操作,當(dāng)線程沒(méi)有執(zhí)行結(jié)束時(shí),activity不會(huì)被回收。
    Context的引用,當(dāng)TextView 等等都會(huì)持有上下文的引用。如果有static drawable,就會(huì)導(dǎo)致該內(nèi)存無(wú)法釋放。
  6. 單例
    單例 是一個(gè)全局的靜態(tài)對(duì)象,當(dāng)持有某個(gè)復(fù)制的類(lèi)A是,A無(wú)法被釋放,內(nèi)存leak。

內(nèi)存泄漏的解決方法:

  1. 內(nèi)存泄漏也許是因?yàn)榛顒?dòng)已經(jīng)被使用完畢,但是仍然在其他地方被引用,導(dǎo)致無(wú)法對(duì)其進(jìn)行回收。我們只需要給對(duì)活動(dòng)進(jìn)行引用的類(lèi)獨(dú)立出來(lái)或者將其變?yōu)殪o態(tài)類(lèi),該類(lèi)隨著活動(dòng)的結(jié)束而結(jié)束,也就沒(méi)有了當(dāng)活動(dòng)結(jié)束但仍然還被其他類(lèi)引用的情況。
  2. 資源性對(duì)象在不使用的時(shí)候,應(yīng)該調(diào)用它的close()函數(shù)將其關(guān)閉掉。
  3. 集合容器中的內(nèi)存泄露 ,我們通常把一些對(duì)象的引用加入到了集合容器(比如ArrayList)中,當(dāng)我們不需要該對(duì)象時(shí),并沒(méi)有把它的引用從集合中清理掉,這樣這個(gè)集合就會(huì)越來(lái)越大。如果這個(gè)集合是static的話,那情況就更嚴(yán)重了。需要在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。
  4. WebView造成的泄露,當(dāng)我們不使用WebView對(duì)象時(shí),應(yīng)該調(diào)用它的destory()函數(shù)來(lái)銷(xiāo)毀它,并釋放其占用的內(nèi)存,否則其長(zhǎng)期占用的內(nèi)存也不能被回收,從而造成內(nèi)存泄露。
    我們應(yīng)該為WebView另外開(kāi)啟一個(gè)進(jìn)程,通過(guò)AIDL與主線程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷(xiāo)毀,從而達(dá)到內(nèi)存的完整釋放。

內(nèi)存溢出的原因及解決方法:

  1. 內(nèi)存溢出原因:
  • 內(nèi)存中加載的數(shù)據(jù)量過(guò)于龐大,如一次從數(shù)據(jù)庫(kù)取出過(guò)多數(shù)據(jù);
  • 集合類(lèi)中有對(duì)對(duì)象的引用,使用完后未清空,產(chǎn)生了堆積,使得JVM不能回收;
  • 代碼中存在死循環(huán)或循環(huán)產(chǎn)生過(guò)多重復(fù)的對(duì)象實(shí)體;
  • 使用的第三方軟件中的BUG;
  • 啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過(guò)小
  1. 內(nèi)存溢出的解決方案:
    第一步,修改JVM啟動(dòng)參數(shù),直接增加內(nèi)存。(-Xms,-Xmx參數(shù)一定不要忘記加。)
    第二步,檢查錯(cuò)誤日志,查看“OutOfMemory”錯(cuò)誤前是否有其 它異?;蝈e(cuò)誤。
    第三步,對(duì)代碼進(jìn)行走查和分析,找出可能發(fā)生內(nèi)存溢出的位置。
    重點(diǎn)排查以下幾點(diǎn):
  • 檢查對(duì)數(shù)據(jù)庫(kù)查詢中,是否有一次獲得全部數(shù)據(jù)的查詢。一般來(lái)說(shuō),如果一次取十萬(wàn)條記錄到內(nèi)存,就可能引起內(nèi)存溢出。這個(gè)問(wèn)題比較隱蔽,在上線前,數(shù)據(jù)庫(kù)中數(shù)據(jù)較少,不容易出問(wèn)題,上線后,數(shù)據(jù)庫(kù)中數(shù)據(jù)多了,一次查詢就有可能引起內(nèi)存溢出。因此對(duì)于數(shù)據(jù)庫(kù)查詢盡量采用分頁(yè)的方式查詢。
  • 檢查代碼中是否有死循環(huán)或遞歸調(diào)用。
  • 檢查是否有大循環(huán)重復(fù)產(chǎn)生新對(duì)象實(shí)體。
  • 檢查L(zhǎng)ist、MAP等集合對(duì)象是否有使用完后,未清除的問(wèn)題。List、MAP等集合對(duì)象會(huì)始終存有對(duì)對(duì)象的引用,使得這些對(duì)象不能被GC回收。

第四步,使用內(nèi)存查看工具動(dòng)態(tài)查看內(nèi)存使用情況

如何避免內(nèi)存泄漏?

  1. 在涉及使用Context時(shí),對(duì)于生命周期比Activity長(zhǎng)的對(duì)象應(yīng)該使用Application的Context。凡是使用Context優(yōu)先考慮Application的Context,當(dāng)然它并不是萬(wàn)能的,對(duì)于有些地方則必須使用Activity的Context。
  2. 對(duì)于需要在靜態(tài)內(nèi)部類(lèi)中使用非靜態(tài)外部成員變量(如:Context、View ),可以在靜態(tài)內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的變量來(lái)避免內(nèi)存泄漏。
  3. 對(duì)于不再需要使用的對(duì)象,顯示的將其賦值為null,比如使用完Bitmap后先調(diào)用recycle(),再賦為null。
  4. 保持對(duì)對(duì)象生命周期的敏感,特別注意單例、靜態(tài)對(duì)象、全局性集合等的生命周期。
  5. 對(duì)于生命周期比Activity長(zhǎng)的內(nèi)部類(lèi)對(duì)象,并且內(nèi)部類(lèi)中使用了外部類(lèi)的成員變量,可以這樣做避免內(nèi)存泄漏:
  • 將內(nèi)部類(lèi)改為靜態(tài)內(nèi)部類(lèi)
  • 靜態(tài)內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的成員變量

如何檢查和分析內(nèi)存泄漏?

因?yàn)閮?nèi)存泄漏是在堆內(nèi)存中,所以對(duì)我們來(lái)說(shuō)并不是可見(jiàn)的。通常我們可以借助MAT、LeakCanary等工具來(lái)檢測(cè)應(yīng)用程序是否存在內(nèi)存泄漏。
1、MAT是一款強(qiáng)大的內(nèi)存分析工具,功能繁多而復(fù)雜。
2、LeakCanary則是由Square開(kāi)源的一款輕量級(jí)的第三方內(nèi)存泄漏檢測(cè)工具,當(dāng)檢測(cè)到程序中產(chǎn)生內(nèi)存泄漏時(shí),它將以最直觀的方式告訴我們哪里產(chǎn)生了內(nèi)存泄漏和導(dǎo)致誰(shuí)泄漏了而不能被回收。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容