天天看點

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

深入記憶體洩露

android應用層的記憶體洩露,其實就是java虛拟機的記憶體洩漏.

(這裡,暫不讨論C/C++本地記憶體的堆洩漏)

1.知識儲備

1.Java記憶體模型

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

相關記憶體對象模型,參照部落格

精講Java記憶體模型
  1. 寄存器(register)。這是最快的儲存區域,這是主要由于它位于處理器内部。然而,寄存器的數量十分有限,是以寄存器是需要由編譯器配置設定的。我們對此沒有直接的控制權,也不可能在自己的程式裡找到寄存器存在的任何蹤迹。

(2) 堆棧(stack)。

在執行函數(方法)時,函數一些内部變量的存儲都可以放在棧上面建立,函數執行結束的時候這些存儲單元就會自動被釋放掉。

位于通用RAM(随機通路存儲器)中。可通過它的“堆棧指針” 獲得處理的直接支援。堆棧指針若向下移,會建立新的記憶體;若向上移,則會釋放那些記憶體。這是一種特别快、特别有效的資料儲存方式,僅次于寄存器。

(3) 堆(heap)。一種通用性的記憶體池(也在RAM區域),堆是不連續的記憶體區域,堆空間比較靈活也特别大。其中儲存了Java對象(<font color=#FF4500>對象裡面的成員變量也在其中</font>)。在堆裡配置設定存儲空間時會花掉更長的時間!也叫做動态記憶體配置設定。

(4) 靜态存儲(static storage)。這兒的“靜态”(Static)是指“位于固定位置”(盡管也在RAM 裡)。程式運作期間,靜态存儲的資料将随時等候調用。可用static關鍵字指出一個對象的特定元素是靜态的。但Java 對象本身永遠都不會置入靜态存儲空間,随着JVM的生命周期結束而結束,即當app完全退出,他才會釋放。

(5) 常數存儲(constant storage)。常數值通常直接置于程式代碼内部。這樣做是安全的,因為它們永遠都不會改變。

(6) 非RAM 存儲(non-storage-RAM)。若資料完全獨立于一個程式之外,則程式不運作時仍可存在,并在程式的控制範圍之外。其中兩個最主要的例子便是“ 流式對象”和“固定對象” 。對于流式對象,對象會變成位元組流,通常會發給另一台機器。而對于固定對象,對象儲存在磁盤中。

2.GC回收機制

引用自

http://blog.csdn.net/jiafu1115/article/details/7024323

首先JVM是對堆進行回收操作.

1.JVM堆中分類

(1) 新域young generation:存儲所有新成生的對象

(2) 舊域old generation:新域中的對象,經過了一定次數的GC循環後,被移入舊域

(3) 永久域PermanentGeneration:存儲類和方法對象,從配置的角度看,這個域是獨立的,不包括在JVM堆内。預設為4M。
           

2.Gc回收流程

1.當eden滿了,觸發young GC;

2.young GC做2件事:一,去掉一部分沒用的object;二,把老的還被引用的object發到survior裡面,等下幾次GC以後,survivor再放到old裡面。

3.當old滿了,觸發full GC。full GC很消耗記憶體,把old,young裡面大部分垃圾回收掉。這個時候使用者線程都會被block。
           

3.Gc回收總結

1.JVM堆的大小決定了GC的運作時間。如果JVM堆的大小超過一定的限度,那麼GC的運作時間會很長。

2.對象生存的時間越長,GC需要的回收時間也越長,影響了回收速度。

3.大多數對象都是短命的,是以,如果能讓這些對象的生存期在GC的一次運作周期内,wonderful!

4.應用程式中,建立與釋放對象的速度決定了垃圾收集的頻率。

5.如果GC一次運作周期超過3-5秒,這會很影響應用程式的運作,如果可以,應該減少JVM堆的大小了。

6.前輩經驗之談:通常情況下,JVM堆的大小應為實體記憶體的80%。
           

3.記憶體抖動

記憶體抖動這個術語可用于描述

在極短時間内配置設定給對象的過程

.

例如,當你在循環語句中配置一系列臨時對象,或者在繪圖功能中配置大量對象時,這相當于内循環,當螢幕需要重新繪制或出現動畫時,你需要一幀幀使用這些功能,不過它會迅速增加你的堆的壓力。

Memory Monitor 記憶體抖動圖例:

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

2.記憶體洩漏對程式造成的影響

1.直接:消耗記憶體,造成系應用OutOfMemory.

一個android應用程式,其實就是一個jvm虛拟機執行個體,而一個jvm的執行個體,在初始的時候,大小不等 16M,32M,64M(根據手機廠商和版本不同而不同),當然大小也可以修改,

參考修改部落格

2.間接:gc回收頻繁 造成應用卡頓ANR.

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

GC回收時間過長導緻卡頓

首先,當記憶體不足的時候,gc會主動回收沒用的記憶體.但是,記憶體回收也是需要時間的.

上圖中,android在畫圖(播放視訊等)的時候,draw到界面的對象,和gc回收垃圾資源之間高頻率交替的執行.就會産生記憶體抖動.

很多資料就會污染記憶體堆,馬上就會有許多GCs啟動,由于這一額外的

記憶體壓力

,也會産生突然

增加的運算造成卡頓現象

任何線程的任何操作都會需要暫停,等待GC操作完成之後,其他操作才能夠繼續運作,

是以垃圾回收運作的次數越少,對性能的影響就越少

3.記憶體洩露的原因

記憶體洩漏的本質:不再用到的對象,被錯誤引用,而無法被回收

未引用對象可以被垃圾回收機制回收,而被引用對象不能被垃圾回收機制回收。

當記憶體不足,gc會回收垃圾記憶體

垃圾記憶體是 沒有别人使用的記憶體,好的記憶體

而記憶體洩漏 是 正在被别人使用的的記憶體,不屬于垃圾記憶體

堆引用記憶體洩漏(Heap leak)

1.靜态變量持有 已經沒有用的對象,導緻對象無法被回收.例如靜态集合類引起記憶體洩露

 2.單例中持有的引用,當activity重新建構後,單例持有的是上一個activity執行個體.導緻上一個無法被回收.

 3.留意事件監聽器和回調.如果一個類注冊了監聽器,但當該類不再被使用後沒有登出監聽器,可能會發生記憶體洩漏。

 4.靜态内部類,持有 對象.

 5.Handler 記憶體洩漏
           

系統資源洩露(Resource Leak)

主要指程式使用系統配置設定的資源比如 Bitmap,handle ,SOCKET等沒有使用相應的函數釋放掉,導緻系統資源的浪費,嚴重可導緻系統效能降低,系統運作不穩定。在try代碼塊裡建立連接配接,在finally裡釋放連接配接,就能夠避免此類記憶體洩漏。

1.bitmap資源未釋放

   2.IO流未關閉

   3.Cursor使用完後未釋放

   4.各種連接配接(網絡,資料庫,socket等) 
           

4.記憶體洩露的分析工具

在android studio 中有以下幾種工具,來進行記憶體洩漏優化分析(eclipse也有類似工具).

1.Memory Monitor 記憶體螢幕.

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me
深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

2.Dump java heap

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

Android Device Monitor(eclipse系列工具類)

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

第三方庫LeakCanary

leakcanary的github位址

5.記憶體洩露的執行個體解決方案

<font color="FF4500">與其說解決記憶體洩漏,更應該說是 避免記憶體洩露 .因為記憶體洩漏一旦産生,即使需要重新開機JVM,也就是重新開機應用,記憶體重新開始計算.即使這樣,也沒法解決</font>

1.單例造成的記憶體洩露

/**
 * Created by ccj on 2016/11/3.
 */

public class SingleExample {

    private static SingleExample mExample;
    private Context context;

    private SingleExample(Context context) {
        this.context = context;
    }

    /**
     * 當MainActivity銷毀再重建後,此時的context,不會走 if (mExample == null) ,而是直接傳回.
     * 此時的Context 還是上一個activity執行個體的Context,是以,上一個activity執行個體并未被釋放,造成記憶體洩漏
     * 
     * 此時,隻需要将application的上下文,作為context即可解決問題
     * @param context
     * @return
     */
    public static SingleExample getExampleInstance(Context context) {
        if (mExample == null) {
            mExample = new SingleExample(context);
        }
        return mExample;

    }

}
           

2.匿名内部類 造成的記憶體洩漏

//android開發經常會繼承實作Activity/Fragment/View,此時如果你使用了匿名類,并被異步線程
//持有了,那要小心了,如果沒有任何措施這樣一定會導緻洩露 
    public class MainActivity extends Activity {
        …
        Runnable ref1 = new MyRunable();
        Runnable ref2 = new Runnable() {
            @Override
            public void run() {
            }
        };
        …
    }

           

3.Handler 造成的記憶體洩漏

Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。

在Android應用的開發中,為了防止記憶體溢出,在處理一些占用記憶體大而且聲明周期較長的對象時候,可以盡量應用軟引用和弱引用技術。

軟/弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛拟機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列可以得知被回收的軟/弱引用的對象清單,進而為緩沖器清除已失效的軟/弱引用。

/*:在 Activity 中避免使用非靜态内部類,比如上面我們将 Handler 聲明為靜态的,
    則其存活期跟 Activity 的生命周期就無關了。同時通過弱引用的方式引入 Activity,
    避免直接将 Activity 作為 context 傳進去,

    推薦使用靜态内部類 + WeakReference 這種方式。每次使用前注意判空

*/
public class SampleActivity extends Activity {
    private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);
    // Go back to the previous Activity.
    finish();
    }
}
           
//改進機制

/*當然在Activity銷毀時候也應該取消相應的任務AsyncTask.cancel(),避免任務在背景執行浪費資源*/。
    public class MainActivity extends AppCompatActivity {
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
        private static class MyHandler extends Handler {
            private WeakReference<Context> reference;
            public MyHandler(Context context) {
                reference = new WeakReference<>(context);
            }
            @Override
            public void handleMessage(Message msg) {
                MainActivity activity = (MainActivity) reference.get();
                if(activity != null){
                    activity.mTextView.setText("");
                }
            }
        }

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView = (TextView)findViewById(R.id.textview);
            loadData();
        }

        private void loadData() {
//...request
            Message message = Message.obtain();
            mHandler.sendMessage(message);
        }
        //注意釋放
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacksAndMessages(null);
        }
    }
           

4.非靜态内部類建立靜态執行個體造成的記憶體洩漏

/*這樣就在Activity内部建立了一個非靜态内部類的單例,每次啟動Activity時都會使用該單例的資料,這樣雖然避免了資源的重複建立,不過這種寫法卻會造成記憶體洩漏,因為非靜态内部類預設會持有外部類的引用,而又使用了該非靜态内部類建立了一個靜态的執行個體,該執行個體的生命周期和應用的一樣長,這就導緻了該靜态執行個體一直會持有該Activity的引用,導緻Activity的記憶體資源不能正常回收。正确的做法為:
    将該内部類設為靜态内部類或将該内部類抽取出來封裝成一個單例,如果需要使用Context,請使用ApplicationContext*/

    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(mManager == null){
                mManager = new TestResource();
            }
//...
        }
        class TestResource {
//...
        }
    }

           

5.資源未關閉造成的記憶體洩漏

對于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者登出,否則這些資源将不會被回收,造成記憶體洩漏。

6.記憶體洩漏總結

1、對于生命周期比Activity長的對象如果需要應該使用ApplicationContext

2、在涉及到Context時先考慮ApplicationContext,當然它并不是萬能的,對于有些地方則必須使用Activity的Context,對于Application,Service,Activity三者的Context的應用場景如下:

這裡寫圖檔描述

深入Android記憶體洩露深入記憶體洩露1.知識儲備2.記憶體洩漏對程式造成的影響3.記憶體洩露的原因4.記憶體洩露的分析工具5.記憶體洩露的執行個體解決方案6.記憶體洩漏總結About Me

其中:NO1表示Application和Service可以啟動一個Activity,不過需要建立一個新的task任務隊列。而對于Dialog而言,隻有在Activity中才能建立

3、對于需要在靜态内部類中使用非靜态外部成員變量(如:Context、View ),可以在靜态内部類中使用弱引用來引用外部類的變量來避免記憶體洩漏

4、對于生命周期比Activity長的内部類對象,并且内部類中使用了外部類的成員變量,可以這樣做避免記憶體洩漏:

将内部類改為靜态内部類

靜态内部類中使用弱引用來引用外部類的成員變量

5、對于不再需要使用的對象,顯示的将其指派為null,比如使用完Bitmap後先調用recycle(),再賦為null

6、保持對對象生命周期的敏感,特别注意單例、靜态對象、全局性集合等的生命周期

About Me

github位址 個人技術成長部落格

繼續閱讀