精品伊人久久大香线蕉,开心久久婷婷综合中文字幕,杏田冲梨,人妻无码aⅴ不卡中文字幕

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
【Android性能優化】內存泄露和內存溢出(OOM)的引發原因及優化方案

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

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

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


一、內存泄露

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

二、內存溢出(OOM)

  • android為每個進程設置Dalvik Heap Size閾值,這個閾值在不同的設備上會因為RAM大小不同而各有差異。如果APP想要分配的內存超過這個閾值,就會發生OOM。

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

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

  • Android 2.x系統,當dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值時候就會發生OOM,也就是說在2.x系統中,考慮native heap對每個進程的內存限制。

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

三、如何避免內存泄漏

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

1、使用輕量的數據結構

  • 使用ArrayMap/SparseArray來代替HashMap,ArrayMap/SparseArray是專門為移動設備設計的高效的數據結構。

  • HashMap實現原理

    • HashMap內部使用一個默認容量為16的數組來存儲數據,采用拉鏈法解決hash沖突(數組+鏈表),如下圖:

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

    • 缺點:(1)就算沒有數據,也需要分配默認16個元素的數組(2)一旦數據量達到Hashmap限定容量的75%,就將按兩倍擴容
  • SparseArray

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

    • 跟SparseArray一樣,內部兩個數組,但是第一個存key的hash值,一個存value,對象按照key的hash值排序,二分查找也是按照hash
    • 查找index時,傳入key,計算出hash,通過二分查找hash數組,確定index

2、不要使用Enum

3、大胖子Bitmap的處理

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

4、不要使用String進行字符串拼接

  • 嚴格的講,String拼接只能歸結到內存抖動中,因為產生的String副本能夠被GC,不會造成內存泄露。

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

5、非靜態內部類內存泄露

  • 在Activity中創建非靜態內部類,非靜態內部類會持有Activity的隱式引用,若內部類生命周期長于Activity,會導致Activity實例無法被回收。(屏幕旋轉后會重新創建Activity實例,如果內部類持有引用,將會導致旋轉前的實例無法被回收)。

  • 解決方案:如果一定要使用內部類,就改用static內部類,在內部類中通過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);//把下載好的任務移除            }        }    }
  • 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、匿名內部類內存泄漏

  • 跟非靜態內部類一樣,匿名內部類也會持有外部類的隱式引用,比較常見的情況有,耗時Handler,耗時Thread,都會造成內存泄漏,解決方式也是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() {    }};@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_home);    //  發送一個10分鐘后執行的一個消息    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 耗時任務        }    }}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持有導致內存泄漏

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

8、記得注銷監聽器

  • 注冊監聽器的時候會add Listener,不要忘記在不需要的時候remove掉Listener。

9、資源文件需要選擇合適的文件夾進行存放

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

10、謹慎使用static對象

  • static對象的生命周期過長,應該謹慎使用

11、謹慎使用單例中不合理的持有

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

12、一定要記得關閉無用連接

  • 在onDestory中關閉Cursor,I/O,數據庫,網絡的連接用完記得關閉。

注意:謹慎使用lager heap

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

四、內存泄露檢測

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

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

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
  • 想要檢測更多,首先注冊一個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
  • 然后對某個可能發生泄露的占用大內存的對象進行監測:
MyApplication.getRefWatcher().watch(sLeaky);
  • 1
  • 1
  • 對Fragment、BroadcastReceiver、Service進行監測:
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

參考文獻

本站僅提供存儲服務,所有內容均由用戶發布,如發現有害或侵權內容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
如何解決加載大圖片時內存溢出的問題
圖片處理OOM總結
Android加載圖片致使內存溢出(Out of Memory異常)
[z]android 應用程序Activity之間數據傳遞與共享的幾種途徑
解析activity之間數據傳遞的方法
關于memory device context內存設備上下文和位圖
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯系客服!

聯系客服

主站蜘蛛池模板: 福贡县| 循化| 瑞丽市| 延安市| 涟水县| 张家川| 昌乐县| 青川县| 乌拉特中旗| 维西| 保德县| 长兴县| 潮州市| 庆城县| 方山县| 徐汇区| 十堰市| 千阳县| 昌平区| 万荣县| 巴林左旗| 南宁市| 武威市| 高台县| 蓝田县| 民乐县| 美姑县| 洪雅县| 建水县| 墨玉县| 色达县| 甘孜县| 东莞市| 香格里拉县| 铜鼓县| 威信县| 阳春市| 余江县| 嫩江县| 河北区| 宜君县|