Android效能優化篇之記憶體優化--記憶體洩漏

weixin_33751566發表於2018-06-04
5748654-0d4793e291325714.jpg
image




引言

1. Android效能優化篇之記憶體優化--記憶體洩漏

2.Android效能優化篇之記憶體優化--記憶體優化分析工具

3.Android效能優化篇之UI渲染效能優化

4.Android效能優化篇之計算效能優化

5.Android效能優化篇之電量優化(1)——電量消耗分析

6.Android效能優化篇之電量優化(2)

7.Android效能優化篇之網路優化

8.Android效能優化篇之Bitmap優化

9.Android效能優化篇之圖片壓縮優化

10.Android效能優化篇之多執行緒併發優化

11.Android效能優化篇之資料傳輸效率優化

12.Android效能優化篇之程式啟動時間效能優化

13.Android效能優化篇之安裝包效能優化

14.Android效能優化篇之服務優化

介紹

今天主要是講記憶體洩漏的產生原因分析,常見的導致記憶體洩漏的示例,以及記憶體洩漏優化的方法。中間穿插著有關java虛擬機器記憶體管理,記憶體分配策略,垃圾收集器的相關知識點。下面就來列出今天講解的大體流程。

講解流程:

1.什麼是記憶體洩漏?
2.android中導致記憶體洩漏的主要幾個點
3.java虛擬機器記憶體管理
4.java記憶體幾種分配策略?
5.垃圾收集器是如何判斷物件是否可回收?
6.什麼是記憶體抖動?
7.記憶體抖動產生的原因?
8.android中4種引用
9.常見的導致記憶體洩漏的示例

下面我們就以上面幾個知識點來進行逐一的分析:


1.什麼是記憶體洩漏?

當一個物件已經不需要在使用了,本應該被回收,而另一個正在使用的物件持有它的引用,導致物件不能被回收。因為不能被及時回收的本該被回收的記憶體,就產生了記憶體洩漏。如果記憶體洩漏太多會導致程式沒有辦法申請記憶體,最後出現記憶體溢位的錯誤。


2.android中導致記憶體洩漏的主要幾個點

android開發中經常出現的點,我有隻有了解了,才能更好的避免。

  • 使用單例模式
  • 使用匿名內部類
  • 使用非同步事件處理機制Handler
  • 使用靜態變數
  • 資源未關閉
  • 設定監聽
  • 使用AsyncTask
  • 使用Bitmap

上面就是我列出的幾個常出現記憶體洩漏的幾個點,下面我們將一一解讀。


3.java虛擬機器記憶體管理

5748654-9cb41898d70dda16.png
img_1_1.png

java虛擬機器記憶體分為虛擬機器棧,本地方法棧,程式計數器,堆,方法區這幾個模組,下面我們就來分析下各個模組。

(1).虛擬機器棧
  • 虛擬機器棧主要的作用就是為執行java方法服務的,是Java方法執行的動態記憶體模型。
* 會導致棧記憶體溢位(StackOverFlowError)
(2).本地方法棧
  • 為執行native方法服務的,其他和虛擬機器棧一樣
(3).程式計數器
  • 是當前執行緒執行的位元組碼行號指示器
  • 處於執行緒獨佔區
  • 如果是執行的是java程式碼,當前值為位元組碼指令的地址,如果是Native,值為undefined
(4).堆
  • 存放物件的例項
  • 垃圾收集器管理的主要區域
  • 分代管理物件
  • 會導致記憶體溢位(OutOfMemoryError)
(5).方法區
  • 存放虛擬機器載入的類資訊,常量,靜態變數,編譯後的程式碼和資料
  • GC主要對方法區進行常量回收和類解除安裝
  • 會出現記憶體溢位(OutOfMemoryError)


4.java記憶體幾種分配策略?

可以結合上面的記憶體分配模型,能很好的理解。

(1).靜態的
  • 靜態儲存區:記憶體在程式編譯期間就已經分配完成,一般來說,這個區域在程式執行期間一直處在
  • 它主要儲存靜態資料,全域性靜態資料和常量
(2).棧式的
  • 執行方法時,儲存區域性變數(編譯期間,已經確定佔用記憶體大小),運算元,動態連結,方法出口
(3).堆式的
  • 也叫動態記憶體分配,主要儲存物件例項,以及已經被載入類的Class物件(用於反射)


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

我們知道記憶體洩漏的原因是應該被回收的物件,不能被及時回收,那麼GC是如何來判斷物件是否為垃圾物件呢?

判斷的方式有兩個:

* 引用計數

物件被引用,引用計數器加1,反之減一,只有引用計數為0,那麼這個物件為垃圾物件

* 可達性

從GCRoot節點物件開始,看是否可以訪問到此物件,如果沒有訪問到則為垃圾物件

可以作為GCRoot物件有以下幾種:

  • 虛擬機器棧中的區域性變數
  • 本地方法棧中的引用物件
  • 方法區中的常量引用物件
  • 方法區中的類屬性引用物件

在native層和早期的虛擬機器一般使用引用計數,但是現在的java虛擬機器大多使用的是可達性。


6.什麼是記憶體抖動?

堆記憶體都有一定的大小,能容納的資料是有限制的,當Java堆的大小太大時,垃圾收集會啟動停止堆中不再應用的物件,來釋放記憶體。當在極短時間內分配給物件和回收物件的過程就是記憶體抖動。

7.記憶體抖動產生的原因?

從術語上來講就是極短時間內分配給物件和回收物件的過程。
一般多是在迴圈語句中建立臨時物件,在繪製時配置大量物件或者執行動畫時建立大量臨時物件
記憶體抖動會帶來UI的卡頓,因為大量的物件建立,會很快消耗剩餘記憶體,導致GC回收,GC會佔用大量的幀繪製時間,從而導致UI卡頓,關於UI卡頓會在後面章節講到。


8.android中4種引用

(1).StrongReference強引用

從不被回收,java虛擬機器停止時,才終止

(2).SoftReference強引用

當記憶體不足時,會主動回收,使用SoftReference使用結合ReferenceQueue構造有效期短

(3).WeakReference強引用

每次垃圾回收時,被回收

(4).PhatomReference強引用

每次垃圾回收時,被回收.結合ReferenceQueue來跟蹤物件被垃圾回收器回收的活動


9.常見的導致記憶體洩漏的示例

(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);

我們看到上面的程式碼就是我們平時使用的單例模式,當然這裡沒有考慮執行緒安全,請忽略。當我們傳遞進來的是Context,那麼當前物件就會持有第一次例項化的Context,如果Context是Activity物件,那麼就會產生記憶體洩漏。因為當前物件ComonUtil是靜態的,生命週期和應用是一樣的,只有應用退出才會釋放,導致Activity不能及時釋放,帶來記憶體洩漏。

怎麼解決呢?

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

    public ComonUtil(Context context) {
        mContext = context.getApplicationContext();
    }
(2).使用非靜態內部類
    /**
     * 非靜態內部類
     */
    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();
                }
            }
        }
    }

我們就以執行緒為例,當Activity呼叫了createNonStaticInnerClass方法,然後退出當前Activity時,因為執行緒還在後臺執行且當前執行緒持有Activity引用,只有等到執行緒執行完畢,Activitiy才能得到釋放,導致記憶體洩漏。
常用的解決方法有很多,第一把執行緒類宣告為靜態的類,如果要用到Activity物件,那麼就作為引數傳入且為WeakReference,第二在Activity的onDestroy時,停止執行緒的執行。

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

這個應該是我們平時使用最多的一種方式,如果當handler中處理的是耗時操作,或者當前訊息佇列中訊息很多時,那當Activity退出時,當前message中持有handler的引用,handler又持有Activity的引用,導致Activity不能及時的釋放,引起記憶體洩漏的問題。
解決handler引起的記憶體洩漏問題常用的兩種方式:
1.和上面解決Thread的方式一樣,
2.在onDestroy中呼叫mHandler.removeCallbacksAndMessages(null)

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

同單例引起的記憶體洩漏。

(5).資源未關閉

常見的就是資料庫遊標沒有關閉,物件檔案流沒有關閉,主要記得關閉就OK了。

(6).設定監聽

常見的是在觀察者模式中出現,我們在退出Acviity時沒有取消監聽,導致被觀察者還持有當前Activity的引用,從而引起記憶體洩漏。
常見的解決方法就是在onPause中注消監聽


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

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

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

和上面同樣的道理,匿名內部類持有外部類的引用,AsyncTask耗時操作導致Activity不能及時釋放,引起記憶體洩漏。
解決方法同上:
1.宣告為靜態類,
2.在onPause中取消任務


(8).使用Bitmap

我們知道當bitmap物件沒有被使用(引用),gc會回收bitmap的佔用記憶體,當時這邊的記憶體指的是java層的,那麼本地記憶體的釋放呢?我們可以通過呼叫bitmap.recycle()來釋放C層上的記憶體,防止本地記憶體洩漏


上面就是這章要將的所有內容,常見的記憶體洩漏的例子也看了,但是我們怎麼知道是否記憶體洩漏了呢,或者說怎麼去發現我們的專案中有記憶體洩漏呢?下一章就來結合上面的例子通過記憶體分析工具來具體的分析。

相關文章