轉(zhuǎn):常見的內(nèi)存泄漏原因及解決方法

(Memory Leak扔傅,內(nèi)存泄漏)

為什么會產(chǎn)生內(nèi)存泄漏?

當(dāng)一個對象已經(jīng)不需要再使用本該被回收時,另外一個正在使用的對象持有它的引用從而導(dǎo)致它不能被回收捺球,這導(dǎo)致本該被回收的對象不能被回收而停留在堆內(nèi)存中,這就產(chǎn)生了內(nèi)存泄漏夕冲。

內(nèi)存泄漏對程序的影響氮兵?

內(nèi)存泄漏是造成應(yīng)用程序OOM的主要原因之一。我們知道Android系統(tǒng)為每個應(yīng)用程序分配的內(nèi)存是有限的歹鱼,而當(dāng)一個應(yīng)用中產(chǎn)生的內(nèi)存泄漏比較多時泣栈,這就難免會導(dǎo)致應(yīng)用所需要的內(nèi)存超過系統(tǒng)分配的內(nèi)存限額,這就造成了內(nèi)存溢出從而導(dǎo)致應(yīng)用Crash弥姻。

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

因為內(nèi)存泄漏是在堆內(nèi)存中,所以對我們來說并不是可見的庭敦。通常我們可以借助MAT疼进、LeakCanary等工具來檢測應(yīng)用程序是否存在內(nèi)存泄漏。

1秧廉、MAT是一款強大的內(nèi)存分析工具伞广,功能繁多而復(fù)雜。

2疼电、LeakCanary則是由Square開源的一款輕量級的第三方內(nèi)存泄漏檢測工具嚼锄,當(dāng)檢測到程序中產(chǎn)生內(nèi)存泄漏時,它將以最直觀的方式告訴我們哪里產(chǎn)生了內(nèi)存泄漏和導(dǎo)致誰泄漏了而不能被回收蔽豺。

常見的內(nèi)存泄漏及解決方法

1区丑、單例造成的內(nèi)存泄漏

由于單例的靜態(tài)特性使得其生命周期和應(yīng)用的生命周期一樣長,如果一個對象已經(jīng)不再需要使用了茫虽,而單例對象還持有該對象的引用刊苍,就會使得該對象不能被正臣让牵回收,從而導(dǎo)致了內(nèi)存泄漏正什。

示例:防止單例導(dǎo)致內(nèi)存泄漏的實例

// 使用了單例模式

// 使用了單例模式
public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

這樣不管傳入什么Context最終將使用Application的Context啥纸,而單例的生命周期和應(yīng)用的一樣長,這樣就防止了內(nèi)存泄漏婴氮。斯棒??主经?

2荣暮、非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例造成的內(nèi)存泄漏

例如,有時候我們可能會在啟動頻繁的Activity中罩驻,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源穗酥,可能會出現(xiàn)如下寫法:

public class MainActivity extends AppCompatActivity {

    private static TestResource mResource = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mResource == null){
            mResource = new TestResource();
        }
        //...
    }
    
    class TestResource {
    //...
    }
}

這樣在Activity內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例,每次啟動Activity時都會使用該單例的數(shù)據(jù)惠遏。雖然這樣避免了資源的重復(fù)創(chuàng)建砾跃,但是這種寫法卻會造成內(nèi)存泄漏。因為非靜態(tài)內(nèi)部類默認會持有外部類的引用节吮,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個靜態(tài)的實例抽高,該實例的生命周期和應(yīng)用的一樣長,這就導(dǎo)致了該靜態(tài)實例一直會持有該Activity的引用透绩,從而導(dǎo)致Activity的內(nèi)存資源不能被正城搪睿回收。

解決方法:將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例帚豪,如果需要使用Context碳竟,就使用Application的Context。

3志鞍、Handler造成的內(nèi)存泄漏

示例:創(chuàng)建匿名內(nèi)部類的靜態(tài)對象

public class MainActivity extends AppCompatActivity {

    private final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // ...
                handler.sendEmptyMessage(0x123);
            }
        });
    }
}

1艺蝴、從Android的角度

當(dāng)Android應(yīng)用程序啟動時订晌,該應(yīng)用程序的主線程會自動創(chuàng)建一個Looper對象和與之關(guān)聯(lián)的MessageQueue。當(dāng)主線程中實例化一個Handler對象后,它就會自動與主線程Looper的MessageQueue關(guān)聯(lián)起來场靴。所有發(fā)送到MessageQueue的Messag都會持有Handler的引用渐溶,所以Looper會據(jù)此回調(diào)Handle的handleMessage()方法來處理消息畅厢。只要MessageQueue中有未處理的Message业崖,Looper就會不斷的從中取出并交給Handler處理。另外呜师,主線程的Looper對象會伴隨該應(yīng)用程序的整個生命周期娶桦。

2、 Java角度

在Java中,非靜態(tài)內(nèi)部類和匿名類內(nèi)部類都會潛在持有它們所屬的外部類的引用衷畦,但是靜態(tài)內(nèi)部類卻不會栗涂。

對上述的示例進行分析,當(dāng)MainActivity結(jié)束時祈争,未處理的消息持有handler的引用斤程,而handler又持有它所屬的外部類也就是MainActivity的引用。這條引用關(guān)系會一直保持直到消息得到處理菩混,這樣阻止了MainActivity被垃圾回收器回收忿墅,從而造成了內(nèi)存泄漏。

解決方法:將Handler類獨立出來或者使用靜態(tài)內(nèi)部類沮峡,這樣便可以避免內(nèi)存泄漏疚脐。

4、線程造成的內(nèi)存泄漏

示例:AsyncTask和Runnable

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new MyRunnable()).start();
        new MyAsyncTask(this).execute();
    }

    class MyAsyncTask extends AsyncTask<Void, Void, Void> {

        // ...

        public MyAsyncTask(Context context) {
            // ...
        }

        @Override
        protected Void doInBackground(Void... params) {
            // ...
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            // ...
        }
    }

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            // ...
        }
    }
}

AsyncTask和Runnable都使用了匿名內(nèi)部類邢疙,那么它們將持有其所在Activity的隱式引用棍弄。如果任務(wù)在Activity銷毀之前還未完成,那么將導(dǎo)致Activity的內(nèi)存資源無法被回收疟游,從而造成內(nèi)存泄漏照卦。

解決方法:將AsyncTask和Runnable類獨立出來或者使用靜態(tài)內(nèi)部類,這樣便可以避免內(nèi)存泄漏乡摹。

5、資源未關(guān)閉造成的內(nèi)存泄漏

對于使用了BraodcastReceiver采转,ContentObserver聪廉,F(xiàn)ile,Cursor故慈,Stream板熊,Bitmap等資源,應(yīng)該在Activity銷毀時及時關(guān)閉或者注銷察绷,否則這些資源將不會被回收干签,從而造成內(nèi)存泄漏。

1)比如在Activity中register了一個BraodcastReceiver拆撼,但在Activity結(jié)束后沒有unregister該BraodcastReceiver容劳。

2)資源性對象比如Cursor,Stream闸度、File文件等往往都用了一些緩沖竭贩,我們在不使用的時候,應(yīng)該及時關(guān)閉它們莺禁,以便它們的緩沖及時回收內(nèi)存留量。它們的緩沖不僅存在于 java虛擬機內(nèi),還存在于java虛擬機外。如果我們僅僅是把它的引用設(shè)置為null楼熄,而不關(guān)閉它們忆绰,往往會造成內(nèi)存泄漏。

3)對于資源性對象在不使用的時候可岂,應(yīng)該調(diào)用它的close()函數(shù)將其關(guān)閉掉错敢,然后再設(shè)置為null。在我們的程序退出時一定要確保我們的資源性對象已經(jīng)關(guān)閉青柄。

4)Bitmap對象不在使用時調(diào)用recycle()釋放內(nèi)存伐债。2.3以后的bitmap應(yīng)該是不需要手動recycle了,內(nèi)存已經(jīng)在java層了致开。

6峰锁、使用ListView時造成的內(nèi)存泄漏

初始時ListView會從BaseAdapter中根據(jù)當(dāng)前的屏幕布局實例化一定數(shù)量的View對象,同時ListView會將這些View對象緩存起來双戳。當(dāng)向上滾動ListView時虹蒋,原先位于最上面的Item的View對象會被回收,然后被用來構(gòu)造新出現(xiàn)在下面的Item飒货。這個構(gòu)造過程就是由getView()方法完成的魄衅,getView()的第二個形參convertView就是被緩存起來的Item的View對象(初始化時緩存中沒有View對象則convertView是null)。

構(gòu)造Adapter時塘辅,沒有使用緩存的convertView晃虫。

解決方法:在構(gòu)造Adapter時,使用緩存的convertView扣墩。

7哲银、集合容器中的內(nèi)存泄露

我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當(dāng)我們不需要該對象時呻惕,并沒有把它的引用從集合中清理掉荆责,這樣這個集合就會越來越大。如果這個集合是static的話亚脆,那情況就更嚴重了做院。

解決方法:在退出程序之前,將集合里的東西clear濒持,然后置為null键耕,再退出程序。

8柑营、WebView造成的泄露

當(dāng)我們不要使用WebView對象時郁竟,應(yīng)該調(diào)用它的destory()函數(shù)來銷毀它,并釋放其占用的內(nèi)存由境,否則其長期占用的內(nèi)存也不能被回收棚亩,從而造成內(nèi)存泄露蓖议。

解決方法:為WebView另外開啟一個進程,通過AIDL與主線程進行通信讥蟆,WebView所在的進程可以根據(jù)業(yè)務(wù)的需要選擇合適的時機進行銷毀勒虾,從而達到內(nèi)存的完整釋放。

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

1修然、在涉及使用Context時,對于生命周期比Activity長的對象應(yīng)該使用Application的Context质况。凡是使用Context優(yōu)先考慮Application的Context愕宋,當(dāng)然它并不是萬能的,對于有些地方則必須使用Activity的Context结榄。對于Application中贝,Service,Activity三者的Context的應(yīng)用場景如下:

image

其中臼朗,NO1表示Application和Service可以啟動一個Activity邻寿,不過需要創(chuàng)建一個新的task任務(wù)隊列。而對于Dialog而言视哑,只有在Activity中才能創(chuàng)建绣否。除此之外三者都可以使用。

2挡毅、對于需要在靜態(tài)內(nèi)部類中使用非靜態(tài)外部成員變量(如:Context蒜撮、View ),可以在靜態(tài)內(nèi)部類中使用弱引用來引用外部類的變量來避免內(nèi)存泄漏跪呈。

3淀弹、對于不再需要使用的對象,顯示的將其賦值為null庆械,比如使用完Bitmap后先調(diào)用recycle(),再賦為null菌赖。

4缭乘、保持對對象生命周期的敏感,特別注意單例琉用、靜態(tài)對象堕绩、全局性集合等的生命周期。

5邑时、對于生命周期比Activity長的內(nèi)部類對象奴紧,并且內(nèi)部類中使用了外部類的成員變量,可以這樣做避免內(nèi)存泄漏:

1)將內(nèi)部類改為靜態(tài)內(nèi)部類

2)靜態(tài)內(nèi)部類中使用弱引用來引用外部類的成員變量

參考

Android內(nèi)存泄漏總結(jié)

本文源自:http://www.reibang.com/p/90caf813682d

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晶丘,一起剝皮案震驚了整個濱河市黍氮,隨后出現(xiàn)的幾起案子唐含,更是在濱河造成了極大的恐慌,老刑警劉巖沫浆,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捷枯,死亡現(xiàn)場離奇詭異,居然都是意外死亡专执,警方通過查閱死者的電腦和手機淮捆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來本股,“玉大人攀痊,你說我怎么就攤上這事≈粝裕” “怎么了苟径?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凿叠。 經(jīng)常有香客問我涩笤,道長,這世上最難降的妖魔是什么盒件? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任蹬碧,我火速辦了婚禮,結(jié)果婚禮上炒刁,老公的妹妹穿的比我還像新娘恩沽。我一直安慰自己,他們只是感情好翔始,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布罗心。 她就那樣靜靜地躺著,像睡著了一般城瞎。 火紅的嫁衣襯著肌膚如雪渤闷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天脖镀,我揣著相機與錄音飒箭,去河邊找鬼。 笑死蜒灰,一個胖子當(dāng)著我的面吹牛弦蹂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播强窖,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼凸椿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翅溺?” 一聲冷哼從身側(cè)響起脑漫,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤髓抑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后窿撬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體启昧,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年劈伴,在試婚紗的時候發(fā)現(xiàn)自己被綠了密末。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡跛璧,死狀恐怖严里,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情追城,我是刑警寧澤刹碾,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站座柱,受9級特大地震影響迷帜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜色洞,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一戏锹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧火诸,春花似錦锦针、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盯荤,卻和暖如春馋吗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秋秤。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工宏粤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人航缀。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像堰怨,于是被迫代替她去往敵國和親芥玉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348

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

  • (Memory Leak备图,內(nèi)存泄漏) 為什么會產(chǎn)生內(nèi)存泄漏灿巧? 當(dāng)一個對象已經(jīng)不需要再使用本該被回收時赶袄,另外一個正在...
    李俊的博客閱讀 107,071評論 5 48
  • Android 內(nèi)存管理的目的 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題。簡單粗...
    晨光光閱讀 1,290評論 1 4
  • Android 內(nèi)存泄漏總結(jié) 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題抠藕。內(nèi)存泄漏...
    _痞子閱讀 1,626評論 0 8
  • 內(nèi)存管理的目的就是讓我們在開發(fā)中怎么有效的避免我們的應(yīng)用出現(xiàn)內(nèi)存泄漏的問題饿肺。內(nèi)存泄漏大家都不陌生了,簡單粗俗的講盾似,...
    宇宙只有巴掌大閱讀 2,361評論 0 12
  • 雨天正如天氣預(yù)報預(yù)計的一樣敬辣!因此今天不用上班。我很久都沒有寫點自已的東西了零院,有時候想 不到一會就消失殆盡溉跃。這我很不...
    跟咸魚有什么區(qū)別玖閱讀 139評論 0 0