日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

【Android性能優(yōu)化】內(nèi)存泄露和內(nèi)存溢出(OOM)的引發(fā)原因及優(yōu)化方案

 jiffes 2017-08-04

轉(zhuǎn)載請注明原文地址:http://blog.csdn.net/mxm691292118/article/details/51020023

我把Android重難點(diǎn)和讀書筆記都整理在github上:https://github.com/miomin/AndroidDifficulty

如果你覺得對你有幫助的話,希望可以star/follow一下喲,我會持續(xù)保持更新。


一、內(nèi)存泄露

  • 垃圾回收器無法回收原本應(yīng)該被回收的對象,這個(gè)對象就引發(fā)了內(nèi)存泄露。
  • 內(nèi)存泄露的危害:
    • (1)過多的內(nèi)存泄露最終會導(dǎo)致內(nèi)存溢出(OOM)
    • (2)內(nèi)存泄露導(dǎo)致可用內(nèi)存不足,會觸發(fā)頻繁GC,不管是Android2.2以前的單線程GC還是現(xiàn)在的CMS和G1,都有一部分的操作會導(dǎo)致用戶線程停止(就是所謂的Stop the world),從而導(dǎo)致UI卡頓。

二、內(nèi)存溢出(OOM)

  • android為每個(gè)進(jìn)程設(shè)置Dalvik Heap Size閾值,這個(gè)閾值在不同的設(shè)備上會因?yàn)镽AM大小不同而各有差異。如果APP想要分配的內(nèi)存超過這個(gè)閾值,就會發(fā)生OOM。

  • ActivityManager.getMemoryClass()可以查詢當(dāng)前APP的Heap Size閾值,單位是MB。

  • 在3.x以前,Bitmap分配在Native heap中,而在4.x之后,Bitmap分配在Dalvik或ART的Java heap中。

  • Android 2.x系統(tǒng),當(dāng)dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值時(shí)候就會發(fā)生OOM,也就是說在2.x系統(tǒng)中,考慮native heap對每個(gè)進(jìn)程的內(nèi)存限制。

  • Android 4.x系統(tǒng),廢除了native的計(jì)數(shù)器,類似bitmap的分配改到dalvik的java heap中申請,只要allocated + 新分配的內(nèi)存 >= dalvik heap 最大值的時(shí)候就會發(fā)生OOM(art運(yùn)行環(huán)境的統(tǒng)計(jì)規(guī)則還是和dalvik保持一致),也就是說在4.x系統(tǒng)中,不考慮native heap對每個(gè)進(jìn)程的內(nèi)存限制,native heap只會收到本機(jī)總內(nèi)存(包括RAM以及SWAP區(qū)或分頁文件)的限制。

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

參考在MDCC 2015中國移動開發(fā)者大會上胡凱前輩的講述,整理總結(jié)。

1、使用輕量的數(shù)據(jù)結(jié)構(gòu)

  • 使用ArrayMap/SparseArray來代替HashMap,ArrayMap/SparseArray是專門為移動設(shè)備設(shè)計(jì)的高效的數(shù)據(jù)結(jié)構(gòu)。

  • HashMap實(shí)現(xiàn)原理

    • HashMap內(nèi)部使用一個(gè)默認(rèn)容量為16的數(shù)組來存儲數(shù)據(jù),采用拉鏈法解決hash沖突(數(shù)組+鏈表),如下圖:
      這里寫圖片描述

    • Entry存儲的內(nèi)容有key、value、hash值、next指針,通過計(jì)算hash(key)%len找到Entry在數(shù)組中的位置。

    • 缺點(diǎn):(1)就算沒有數(shù)據(jù),也需要分配默認(rèn)16個(gè)元素的數(shù)組(2)一旦數(shù)據(jù)量達(dá)到Hashmap限定容量的75%,就將按兩倍擴(kuò)容
  • SparseArray

    • 支持int類型,避免自動裝箱,但是也只支持int類型的key
    • 內(nèi)部通過兩個(gè)數(shù)組來進(jìn)行數(shù)據(jù)存儲的,一個(gè)存儲key,另外一個(gè)存儲value
    • 因?yàn)閗ey是int,在查找時(shí),采用二分查找,效率高,SparseArray存儲的元素都是按元素的key值從小到大排列好的。 (Hashmap通過遍歷Entry數(shù)組來獲取對象)
    • 默認(rèn)初始size為0,每次增加元素,size++
    • SparseArray中put方法的源碼如下:
  • ArrayMap

    • 跟SparseArray一樣,內(nèi)部兩個(gè)數(shù)組,但是第一個(gè)存key的hash值,一個(gè)存value,對象按照key的hash值排序,二分查找也是按照hash
    • 查找index時(shí),傳入key,計(jì)算出hash,通過二分查找hash數(shù)組,確定index

2、不要使用Enum

3、大胖子Bitmap的處理

  • Bitmap壓縮
  • Lru機(jī)制處理Bitmap(下一節(jié)博客會詳細(xì)講解),也可以使用那些有名的圖片緩存框架。

4、不要使用String進(jìn)行字符串拼接

  • 嚴(yán)格的講,String拼接只能歸結(jié)到內(nèi)存抖動中,因?yàn)楫a(chǎn)生的String副本能夠被GC,不會造成內(nèi)存泄露。

  • 頻繁的字符串拼接,使用StringBuffer或者StringBuilder代替String,可以在一定程度上避免OOM和內(nèi)存抖動。

5、非靜態(tài)內(nèi)部類內(nèi)存泄露

  • 在Activity中創(chuàng)建非靜態(tài)內(nèi)部類,非靜態(tài)內(nèi)部類會持有Activity的隱式引用,若內(nèi)部類生命周期長于Activity,會導(dǎo)致Activity實(shí)例無法被回收。(屏幕旋轉(zhuǎn)后會重新創(chuàng)建Activity實(shí)例,如果內(nèi)部類持有引用,將會導(dǎo)致旋轉(zhuǎn)前的實(shí)例無法被回收)。

  • 解決方案:如果一定要使用內(nèi)部類,就改用static內(nèi)部類,在內(nèi)部類中通過WeakReference的方式引用外界資源。

  • 正確的代碼示例:

static class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {

        private String url;
        private WeakReference<PhotoAdapter> photoAdapter;

        public ImageDownloadTask(PhotoAdapter photoAdapter) {
            this.photoAdapter = new WeakReference<PhotoAdapter>(photoAdapter);
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            //在后臺開始下載圖片
            url = params[0];
            Bitmap bitmap = photoAdapter.get().loadBitmap(url);
            if (bitmap != null) {
                //把下載好的圖片放入LruCache中
                String key = MD5Tools.decodeString(url);
                photoAdapter.get().put(key, bitmap);
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            //把下載好的圖片顯示出來
            ImageView mImageView = (ImageView) photoAdapter.get().mGridView.get().findViewWithTag(MD5Tools.decodeString(url));
            if (mImageView != null && bitmap != null) {
                mImageView.setImageBitmap(bitmap);
                photoAdapter.get().mDownloadTaskList.remove(this);//把下載好的任務(wù)移除
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

6、匿名內(nèi)部類內(nèi)存泄漏

  • 跟非靜態(tài)內(nèi)部類一樣,匿名內(nèi)部類也會持有外部類的隱式引用,比較常見的情況有,耗時(shí)Handler,耗時(shí)Thread,都會造成內(nèi)存泄漏,解決方式也是static+WeakReference,下面給出正確寫法。

  • Handler的正確寫法:

private static class MyHandler extends Handler {

    private final WeakReference<Context> context;

    private MyHandler(Context context) {
        this.context = new WeakReference<Context>(context);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {

        }
    }
}

private final MyHandler mHandler = new MyHandler(this);

private static final Runnable sRunnable = new Runnable() {
    @Override
    public void run() {

    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);
    //  發(fā)送一個(gè)10分鐘后執(zhí)行的一個(gè)消息
    mHandler.postDelayed(sRunnable, 600000);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • Thread的正確寫法:
private static class MyThread extends Thread {

    @Override
    public void run() {
        while (true) {
            // TODO 耗時(shí)任務(wù)
        }
    }
}

new MyThread().start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

7、Context持有導(dǎo)致內(nèi)存泄漏

  • Activity Context被傳遞到其他實(shí)例中,這可能導(dǎo)致自身被引用而發(fā)生泄漏。
  • 解決:對于大部分非必須使用Activity Context的情況(創(chuàng)建Dialog的Context必須是Activity Context),應(yīng)該使用Application Context。

8、記得注銷監(jiān)聽器

  • 注冊監(jiān)聽器的時(shí)候會add Listener,不要忘記在不需要的時(shí)候remove掉Listener。

9、資源文件需要選擇合適的文件夾進(jìn)行存放

  • hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設(shè)備上會經(jīng)過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那么根據(jù)換算關(guān)系,xxhdpi的手機(jī)去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,內(nèi)存占用是會顯著提高的。對于不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。

10、謹(jǐn)慎使用static對象

  • static對象的生命周期過長,應(yīng)該謹(jǐn)慎使用

11、謹(jǐn)慎使用單例中不合理的持有

  • 單例中的對象生命周期與應(yīng)用一致,注意不要在單例中進(jìn)行不必要的外界引用持有。如果一定要引用外部變量,需要在外部變量生命周期結(jié)束的時(shí)候接觸引用(賦為null)。

12、一定要記得關(guān)閉無用連接

  • 在onDestory中關(guān)閉Cursor,I/O,數(shù)據(jù)庫,網(wǎng)絡(luò)的連接用完記得關(guān)閉。

注意:謹(jǐn)慎使用lager heap

  • 不同的設(shè)備有不容的RAM,他們?yōu)閼?yīng)用程序設(shè)置了不同大小的Heap的閾值。雖然可以通過largeHeap=true的屬性來為應(yīng)用獲得一個(gè)更大的heap空間,然后通過getLargeMemoryClass()來獲取到這個(gè)更大的heap閾值。但是你要注意,largeHeap只是為了一些本來就需要大量內(nèi)存的APP存在,比如圖墻和圖片編輯軟件。所以,不要隨意的使用large heap,否則會影響系統(tǒng)整體的用戶體驗(yàn),會使每次gc時(shí)間更長。

四、內(nèi)存泄露檢測

  • 這里介紹LeakCanary,一款非常好用的內(nèi)存泄露檢測工具,安裝在手機(jī)上,能夠通過Log的方式告訴你是哪塊代碼發(fā)生了內(nèi)存泄露。

  • 使用方法,在Application中install LeakCanary(默認(rèn)只能檢測Activity內(nèi)容的內(nèi)存泄露):

public class MyApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 想要檢測更多,首先注冊一個(gè)RefWatcher:
public class MyApplication extends Application {

    private static RefWatcher sRefWatcher;

    @Override
    public void onCreate() {
        super.onCreate();
        sRefWatcher = LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher() {
        return sRefWatcher;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 然后對某個(gè)可能發(fā)生泄露的占用大內(nèi)存的對象進(jìn)行監(jiān)測:
MyApplication.getRefWatcher().watch(sLeaky);
  • 1
  • 1
  • 對Fragment、BroadcastReceiver、Service進(jìn)行監(jiān)測:
public class MyFragment extends Fragment {
    @Override
    public void onDestroy() {
        super.onDestroy();
        MyApplication.getRefWatcher().watch(this);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

參考文獻(xiàn)

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多