Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存泄漏

image




引言

1. Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存泄漏

2.Android性能優(yōu)化篇之內(nèi)存優(yōu)化--內(nèi)存優(yōu)化分析工具

3.Android性能優(yōu)化篇之UI渲染性能優(yōu)化

4.Android性能優(yōu)化篇之計(jì)算性能優(yōu)化

5.Android性能優(yōu)化篇之電量優(yōu)化(1)——電量消耗分析

6.Android性能優(yōu)化篇之電量優(yōu)化(2)

7.Android性能優(yōu)化篇之網(wǎng)絡(luò)優(yōu)化

8.Android性能優(yōu)化篇之Bitmap優(yōu)化

9.Android性能優(yōu)化篇之圖片壓縮優(yōu)化

10.Android性能優(yōu)化篇之多線程并發(fā)優(yōu)化

11.Android性能優(yōu)化篇之?dāng)?shù)據(jù)傳輸效率優(yōu)化

12.Android性能優(yōu)化篇之程序啟動時(shí)間性能優(yōu)化

13.Android性能優(yōu)化篇之安裝包性能優(yōu)化

14.Android性能優(yōu)化篇之服務(wù)優(yōu)化

介紹

今天主要是講內(nèi)存泄漏的產(chǎn)生原因分析伏伐,常見的導(dǎo)致內(nèi)存泄漏的示例,以及內(nèi)存泄漏優(yōu)化的方法膘滨。中間穿插著有關(guān)java虛擬機(jī)內(nèi)存管理昌妹,內(nèi)存分配策略捶枢,垃圾收集器的相關(guān)知識點(diǎn)。下面就來列出今天講解的大體流程飞崖。

講解流程:

1.什么是內(nèi)存泄漏烂叔?
2.android中導(dǎo)致內(nèi)存泄漏的主要幾個(gè)點(diǎn)
3.java虛擬機(jī)內(nèi)存管理
4.java內(nèi)存幾種分配策略?
5.垃圾收集器是如何判斷對象是否可回收固歪?
6.什么是內(nèi)存抖動蒜鸡?
7.內(nèi)存抖動產(chǎn)生的原因?
8.android中4種引用
9.常見的導(dǎo)致內(nèi)存泄漏的示例

下面我們就以上面幾個(gè)知識點(diǎn)來進(jìn)行逐一的分析:


1.什么是內(nèi)存泄漏牢裳?

當(dāng)一個(gè)對象已經(jīng)不需要在使用了逢防,本應(yīng)該被回收,而另一個(gè)正在使用的對象持有它的引用蒲讯,導(dǎo)致對象不能被回收忘朝。因?yàn)椴荒鼙患皶r(shí)回收的本該被回收的內(nèi)存,就產(chǎn)生了內(nèi)存泄漏判帮。如果內(nèi)存泄漏太多會導(dǎo)致程序沒有辦法申請內(nèi)存局嘁,最后出現(xiàn)內(nèi)存溢出的錯(cuò)誤。


2.android中導(dǎo)致內(nèi)存泄漏的主要幾個(gè)點(diǎn)

android開發(fā)中經(jīng)常出現(xiàn)的點(diǎn)脊另,我有只有了解了导狡,才能更好的避免约巷。

  • 使用單例模式
  • 使用匿名內(nèi)部類
  • 使用異步事件處理機(jī)制Handler
  • 使用靜態(tài)變量
  • 資源未關(guān)閉
  • 設(shè)置監(jiān)聽
  • 使用AsyncTask
  • 使用Bitmap

上面就是我列出的幾個(gè)常出現(xiàn)內(nèi)存泄漏的幾個(gè)點(diǎn)偎痛,下面我們將一一解讀。


3.java虛擬機(jī)內(nèi)存管理

img_1_1.png

java虛擬機(jī)內(nèi)存分為虛擬機(jī)棧独郎,本地方法棧踩麦,程序計(jì)數(shù)器,堆氓癌,方法區(qū)這幾個(gè)模塊谓谦,下面我們就來分析下各個(gè)模塊。

(1).虛擬機(jī)棧
  • 虛擬機(jī)棧主要的作用就是為執(zhí)行java方法服務(wù)的贪婉,是Java方法執(zhí)行的動態(tài)內(nèi)存模型反粥。
* 會導(dǎo)致棧內(nèi)存溢出(StackOverFlowError)
(2).本地方法棧
  • 為執(zhí)行native方法服務(wù)的,其他和虛擬機(jī)棧一樣
(3).程序計(jì)數(shù)器
  • 是當(dāng)前線程執(zhí)行的字節(jié)碼行號指示器
  • 處于線程獨(dú)占區(qū)
  • 如果是執(zhí)行的是java代碼,當(dāng)前值為字節(jié)碼指令的地址,如果是Native才顿,值為undefined
(4).堆
  • 存放對象的實(shí)例
  • 垃圾收集器管理的主要區(qū)域
  • 分代管理對象
  • 會導(dǎo)致內(nèi)存溢出(OutOfMemoryError)
(5).方法區(qū)
  • 存放虛擬機(jī)加載的類信息莫湘,常量,靜態(tài)變量郑气,編譯后的代碼和數(shù)據(jù)
  • GC主要對方法區(qū)進(jìn)行常量回收和類卸載
  • 會出現(xiàn)內(nèi)存溢出(OutOfMemoryError)


4.java內(nèi)存幾種分配策略幅垮?

可以結(jié)合上面的內(nèi)存分配模型,能很好的理解尾组。

(1).靜態(tài)的
  • 靜態(tài)存儲區(qū):內(nèi)存在程序編譯期間就已經(jīng)分配完成忙芒,一般來說,這個(gè)區(qū)域在程序運(yùn)行期間一直處在
  • 它主要儲存靜態(tài)數(shù)據(jù)讳侨,全局靜態(tài)數(shù)據(jù)和常量
(2).棧式的
  • 執(zhí)行方法時(shí)呵萨,存儲局部變量(編譯期間,已經(jīng)確定占用內(nèi)存大小)爷耀,操作數(shù)甘桑,動態(tài)鏈接,方法出口
(3).堆式的
  • 也叫動態(tài)內(nèi)存分配歹叮,主要存儲對象實(shí)例跑杭,以及已經(jīng)被加載類的Class對象(用于反射)


5.垃圾收集器是如何判斷對象是否可回收?

我們知道內(nèi)存泄漏的原因是應(yīng)該被回收的對象咆耿,不能被及時(shí)回收德谅,那么GC是如何來判斷對象是否為垃圾對象呢?

判斷的方式有兩個(gè):

* 引用計(jì)數(shù)

對象被引用萨螺,引用計(jì)數(shù)器加1窄做,反之減一,只有引用計(jì)數(shù)為0,那么這個(gè)對象為垃圾對象

* 可達(dá)性

從GCRoot節(jié)點(diǎn)對象開始慰技,看是否可以訪問到此對象椭盏,如果沒有訪問到則為垃圾對象

可以作為GCRoot對象有以下幾種:

  • 虛擬機(jī)棧中的局部變量
  • 本地方法棧中的引用對象
  • 方法區(qū)中的常量引用對象
  • 方法區(qū)中的類屬性引用對象

在native層和早期的虛擬機(jī)一般使用引用計(jì)數(shù),但是現(xiàn)在的java虛擬機(jī)大多使用的是可達(dá)性吻商。


6.什么是內(nèi)存抖動掏颊?

堆內(nèi)存都有一定的大小,能容納的數(shù)據(jù)是有限制的艾帐,當(dāng)Java堆的大小太大時(shí)乌叶,垃圾收集會啟動停止堆中不再應(yīng)用的對象,來釋放內(nèi)存柒爸。當(dāng)在極短時(shí)間內(nèi)分配給對象和回收對象的過程就是內(nèi)存抖動准浴。

7.內(nèi)存抖動產(chǎn)生的原因?

從術(shù)語上來講就是極短時(shí)間內(nèi)分配給對象和回收對象的過程捎稚。
一般多是在循環(huán)語句中創(chuàng)建臨時(shí)對象乐横,在繪制時(shí)配置大量對象或者執(zhí)行動畫時(shí)創(chuàng)建大量臨時(shí)對象
內(nèi)存抖動會帶來UI的卡頓求橄,因?yàn)榇罅康膶ο髣?chuàng)建,會很快消耗剩余內(nèi)存葡公,導(dǎo)致GC回收谈撒,GC會占用大量的幀繪制時(shí)間,從而導(dǎo)致UI卡頓匾南,關(guān)于UI卡頓會在后面章節(jié)講到啃匿。


8.android中4種引用

(1).StrongReference強(qiáng)引用

從不被回收,java虛擬機(jī)停止時(shí)蛆楞,才終止

(2).SoftReference軟引用

當(dāng)內(nèi)存不足時(shí)溯乒,會主動回收,使用SoftReference使用結(jié)合ReferenceQueue構(gòu)造有效期短

(3).WeakReference弱引用

每次垃圾回收時(shí)豹爹,被回收

(4).PhatomReference虛引用

每次垃圾回收時(shí)裆悄,被回收.結(jié)合ReferenceQueue來跟蹤對象被垃圾回收器回收的活動


9.常見的導(dǎo)致內(nèi)存泄漏的示例

(1).使用單例模式
    private static ComonUtil mInstance = null;
    private Context mContext = null;

    public ComonUtil(Context context) {
        mContext = context;
    }

    public static ComonUtil getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new ComonUtil(context);
        }
        return mInstance;
    }

使用:

  ComonUtil mComonUtil = ComonUtil.getInstance(this);

我們看到上面的代碼就是我們平時(shí)使用的單例模式,當(dāng)然這里沒有考慮線程安全臂聋,請忽略光稼。當(dāng)我們傳遞進(jìn)來的是Context,那么當(dāng)前對象就會持有第一次實(shí)例化的Context孩等,如果Context是Activity對象艾君,那么就會產(chǎn)生內(nèi)存泄漏。因?yàn)楫?dāng)前對象ComonUtil是靜態(tài)的肄方,生命周期和應(yīng)用是一樣的冰垄,只有應(yīng)用退出才會釋放,導(dǎo)致Activity不能及時(shí)釋放权她,帶來內(nèi)存泄漏虹茶。

怎么解決呢?

常見的有兩種方式隅要,第一就是傳入ApplicationContext蝴罪,第二CommonUtil中取context.getApplicationContext()。

    public ComonUtil(Context context) {
        mContext = context.getApplicationContext();
    }
(2).使用非靜態(tài)內(nèi)部類
    /**
     * 非靜態(tài)內(nèi)部類
     */
    public void createNonStaticInnerClass(){
        CustomThread mCustomThread = new CustomThread();
        mCustomThread.start();
    }

    public class CustomThread extends Thread{
        @Override
        public void run() {
            super.run();
            while (true){
                try {
                    Thread.sleep(5000);
                    Log.i(TAG,"CustomThread ------- 打印");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

我們就以線程為例步清,當(dāng)Activity調(diào)用了createNonStaticInnerClass方法要门,然后退出當(dāng)前Activity時(shí),因?yàn)榫€程還在后臺執(zhí)行且當(dāng)前線程持有Activity引用尼啡,只有等到線程執(zhí)行完畢暂衡,Activitiy才能得到釋放询微,導(dǎo)致內(nèi)存泄漏崖瞭。
常用的解決方法有很多,第一把線程類聲明為靜態(tài)的類撑毛,如果要用到Activity對象书聚,那么就作為參數(shù)傳入且為WeakReference,第二在Activity的onDestroy時(shí)唧领,停止線程的執(zhí)行。

    public static class CustomThread extends Thread{
        private WeakReference<MainActivity> mActivity;
        public CustomThread(MainActivity activity){
            mActivity = new WeakReference<MainActivity>(activity)
        }
    }
(3).使用異步事件處理機(jī)制Handler
    /**
     * 異步消息處理機(jī)制  -- handler機(jī)制
     */
    public void createHandler(){
        mHandler.sendEmptyMessage(0);
    }
    public Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            //處理耗時(shí)操作   
            return false;
        }
    });

這個(gè)應(yīng)該是我們平時(shí)使用最多的一種方式雌续,如果當(dāng)handler中處理的是耗時(shí)操作斩个,或者當(dāng)前消息隊(duì)列中消息很多時(shí),那當(dāng)Activity退出時(shí)驯杜,當(dāng)前message中持有handler的引用受啥,handler又持有Activity的引用,導(dǎo)致Activity不能及時(shí)的釋放鸽心,引起內(nèi)存泄漏的問題滚局。
解決handler引起的內(nèi)存泄漏問題常用的兩種方式:
1.和上面解決Thread的方式一樣,
2.在onDestroy中調(diào)用mHandler.removeCallbacksAndMessages(null)

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
(4).使用靜態(tài)變量

同單例引起的內(nèi)存泄漏顽频。

(5).資源未關(guān)閉

常見的就是數(shù)據(jù)庫游標(biāo)沒有關(guān)閉藤肢,對象文件流沒有關(guān)閉,主要記得關(guān)閉就OK了糯景。

(6).設(shè)置監(jiān)聽

常見的是在觀察者模式中出現(xiàn)嘁圈,我們在退出Acviity時(shí)沒有取消監(jiān)聽,導(dǎo)致被觀察者還持有當(dāng)前Activity的引用蟀淮,從而引起內(nèi)存泄漏最住。
常見的解決方法就是在onPause中注消監(jiān)聽


(7).使用AsyncTask
    public AsyncTask<Object, Object, Object> mTask = new AsyncTask<Object, Object, Object>() {

        @Override
        protected Object doInBackground(Object... params) {
            //耗時(shí)操作
            return null;
        }

        @Override
        protected void onPostExecute(Object o) {
        
        }   
    };

和上面同樣的道理,匿名內(nèi)部類持有外部類的引用怠惶,AsyncTask耗時(shí)操作導(dǎo)致Activity不能及時(shí)釋放温学,引起內(nèi)存泄漏。
解決方法同上:
1.聲明為靜態(tài)類甚疟,
2.在onPause中取消任務(wù)


(8).使用Bitmap

我們知道當(dāng)bitmap對象沒有被使用(引用)仗岖,gc會回收bitmap的占用內(nèi)存,當(dāng)時(shí)這邊的內(nèi)存指的是java層的览妖,那么本地內(nèi)存的釋放呢轧拄?我們可以通過調(diào)用bitmap.recycle()來釋放C層上的內(nèi)存,防止本地內(nèi)存泄漏


上面就是這章要將的所有內(nèi)容讽膏,常見的內(nèi)存泄漏的例子也看了檩电,但是我們怎么知道是否內(nèi)存泄漏了呢,或者說怎么去發(fā)現(xiàn)我們的項(xiàng)目中有內(nèi)存泄漏呢府树?下一章就來結(jié)合上面的例子通過內(nèi)存分析工具來具體的分析俐末。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奄侠,隨后出現(xiàn)的幾起案子卓箫,更是在濱河造成了極大的恐慌,老刑警劉巖垄潮,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烹卒,死亡現(xiàn)場離奇詭異闷盔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)旅急,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門逢勾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人藐吮,你說我怎么就攤上這事溺拱。” “怎么了谣辞?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵盟迟,是天一觀的道長。 經(jīng)常有香客問我潦闲,道長攒菠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任歉闰,我火速辦了婚禮辖众,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘和敬。我一直安慰自己凹炸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布昼弟。 她就那樣靜靜地躺著啤它,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舱痘。 梳的紋絲不亂的頭發(fā)上变骡,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音芭逝,去河邊找鬼塌碌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛旬盯,可吹牛的內(nèi)容都是我干的台妆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼胖翰,長吁一口氣:“原來是場噩夢啊……” “哼接剩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萨咳,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤懊缺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后某弦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桐汤,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年靶壮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怔毛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腾降,死狀恐怖拣度,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情螃壤,我是刑警寧澤抗果,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站奸晴,受9級特大地震影響冤馏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寄啼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一逮光、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧墩划,春花似錦涕刚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至察净,卻和暖如春驾茴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氢卡。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工沟涨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人异吻。 一個(gè)月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓裹赴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诀浪。 傳聞我的和親對象是個(gè)殘疾皇子棋返,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

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