Android性能優化(二)-內存優化

1、常見內存問題

常見的內存問題主要分爲三種:java

一、內存抖動

指在短期內有大量的對象被建立或者被回收的現象android

危害:gc頻繁,gc時會發生stw,致使卡頓git

二、內存泄漏

指程序在申請內存後,沒法釋放已申請的內存空間github

危害:一次內存泄漏彷佛不會有很大影響,但內存泄漏堆積後的後果就是內存溢出,致使崩潰。即便沒發生內存溢出,佔據較多內存後,會引起gc,致使卡頓數據庫

三、內存溢出

指程序運行要用到的內存大於能提供的最大內存,程序運行不了的現象markdown

危害:程序沒法運行app

2、經常使用的內存分析工具

一、Memory Profiler

AndroidStudio提供的內存分析工具,經常使用於直觀判斷是否有內存抖動和內存泄漏dom

如圖所示,能夠直觀的看出來內存的波動,從而判斷是否有內存問題ide

二、Mat

Mat能夠用於深度分析內存抖動和內存泄漏工具

使用方法以下:

一、先用Memory Profiler的堆轉儲功能,記錄下一段時間內的內存狀況,存儲爲hprof文件。

二、使用hprof-conv工具,轉換從Memory Profiler保存下來的hprof文件。由於Mat沒法解析直接從Memory Profiler保存下來的文件,可是Android官方提供了轉換工具hprof-conv。hprof-conv在安卓SDK中的platform-tools目錄中。使用命令以下

hprof-conv 舊hprof文件 轉換後的hprof文件

三、使用Mat解析轉換後的hprof文件

點擊下圖該按鈕打開hprof文件

四、Mat的基本功能

查看某個類引用相關

  • with incoming references : 表示該類被 哪些外部對應 引用了
  • with outgoing references : 表示 該類 持有了 哪些外部對象的引用

查看GC Root

在dominator_tree中,查找Activity->Path To GC Roots->with all references

在本案例中,結果以下:

結果出了系統類引用外,Activity被單例PluginManager給引用了,形成了泄漏,Activity沒法釋放

三、LeakCanary

LeakCanary是一個自動化的內存泄漏分析工具

使用方法

一、增長依賴

compile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
複製代碼

二、Application中開啓檢測功能

public class MyApplication extends Application {


    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(this);
    }
}
複製代碼

三、操做app,若是有泄漏則會通知出如今手機通知欄中,打開後,可看到如下這種內存泄漏的界面

LeakCanary原理

一、RefWatcher.watch() 建立一個 KeyedWeakReference 到要被監控的對象。

二、而後在後臺線程檢查引用是否被清除,若是沒有,調用GC。

三、若是引用仍是未被清除,把 heap 內存 dump 到 APP 對應的文件系統中的一個 .hprof 文件中。

四、在另一個進程中的 HeapAnalyzerService 有一個 HeapAnalyzer 使用HAHA 解析這個文件。

五、得益於惟一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內存泄露。

六、HeapAnalyzer 計算 到 GC roots 的最短強引用路徑,並肯定是不是泄露。若是是的話,創建致使泄露的引用鏈。

七、引用鏈傳遞到 APP 進程中的 DisplayLeakService, 並以通知的形式展現出來。

判斷對象是否能被回收,是使用弱引用結合引用隊列實現,當一個對象能被回收,則會被加入到關聯的引用隊列中。以此來判斷對象是否泄漏

下面的代碼是參照LeakCanary檢測是否泄露的源碼所寫的一段檢測代碼,基本能夠體現出leakCanary的泄露檢測原理

class RefWatcher {

    private val referenceQueue = ReferenceQueue<Any>()

    private val retainedKeys = HashSet<String>()

    private val gcTrigger = GcTrigger.Default

    private val executor = Executors.newSingleThreadExecutor()

    /** * 在Activity destroy時,執行判斷是否泄露 */
    fun watch(obj : Any) {
        val key = UUID.randomUUID().toString()
        retainedKeys.add(key)
        val reference = KeyedWeakReference(key, obj, referenceQueue)
        executor.execute {
            //清除被加到隊列中的對象
            removeReachableReferences()
            //第一次判斷
            if(gone(reference)) {
                //沒有泄露
            } else {
                //跑一遍gc,gc的同時會讓線程掛起100ms
                gcTrigger.runGc()
                //再清除一次被加入ReferenceQueue中的引用
                removeReachableReferences()
                //再判斷一次
                if(gone(reference)) {
                    //沒有泄露
                } else{
                    //判斷爲泄露
                }
            }
            
        }
    }

    /** * 當一個對象能夠被gc回收時,會被加入referenceQueue * 被加入到referenceQueue的引用,則斷定對象不會泄露,從retainsKey中移除 */
    private fun removeReachableReferences() {
        var ref  = referenceQueue.poll() as KeyedWeakReference
        while(ref != null) {
            retainedKeys.remove(ref.key)
            ref = referenceQueue.poll() as KeyedWeakReference
        }
    }

    /** * retainedKeys中不包含的,則斷定爲可回收,不會泄露 */
    private fun gone(reference: KeyedWeakReference) : Boolean {
        return !retainedKeys.contains(reference.key)
    }

}
複製代碼

四、Matrix的ResourceCanary

ResourceCanary是騰訊的APM中檢測內存泄漏的模塊,該模塊實現和leakCanary十分接近,有兩個點不一樣:

一、leakCanary在二次判斷前,執行gc,後線程掛起100秒,由於調用gc後,虛擬機不必定立刻執行gc,但這種判斷方法存在誤判的可能,可能確實沒有gc。ResourceCanary改良了作法,在gc前,設定一個普通可回收的對象的弱引用,將這個引用做爲哨兵,當有gc發生,則這個對象被回收,反之,則這個對象還存活。依靠這個對象判斷虛擬機是否真正執行了gc。

二、leakCanary分析的是完整的hprof文件,而ResourceCanary是先dump完整的hprof文件,後裁剪。比leakCanary多了一步裁剪工做,減少了hprof文件大小

可是,不管是leakCanary仍是ResourceCanary都不適合線上直接使用,由於完整的hprof文件很大,可能高達幾百兆,不適合線上執行。

五、字節跳動的Tailor

Tailor是一個內存快照裁剪壓縮工具,經過hook技術在c層write時裁剪文件,不用存完整的hprof文件,避免dump下來完整的hprof文件,可線上使用

該庫的具體實現可查看:https://github.com/bytedance/tailor

六、StrictMode

StriceMode是谷歌官方出的運行時檢測工具,主要包含虛擬機策略和線程策略,虛擬機策略能夠檢測Activity泄漏、Sql對象泄漏,某個類實例個數是否超出等,檢測結果能夠從log中打印

StrictMode.setVmPolicy(
                new StrictMode.VmPolicy.Builder()
                        //檢測Activity泄漏
                        .detectActivityLeaks()
                        //檢測數據庫對象泄漏
                        .detectLeakedSqlLiteObjects()
                        //檢測某個類的實例個數
                        .setClassInstanceLimit(PluginManager.class, 1)
                        //檢測結果在log中打印,也可選擇若是泄漏就退出應用
                        .penaltyLog()
                        .build());
複製代碼

3、內存抖動和內存泄漏

一、內存抖動

如何發現:使用memory profiler 觀察,內存呈鋸齒狀,忽高忽低

常見緣由:通常是程序頻繁建立對象引發。或者是內存泄露致使了內存不足,再申請頻繁引起gc致使

危害:頻繁gc,致使卡頓

解決方案:一般可利用Memory Profiler觀察內存抖動,查看內存分配較多,實例較多的部分,找出堆棧排查

二、內存泄漏

定義: 內存中存在沒有用的對象回收不了

危害: 致使可用內存逐漸減小,嚴重時可能致使內存溢出

解決: 經過memory profiler 初步排查,可用內存是否逐漸減小,若是存在這種現象則可能有內存泄露。

在as.中使用堆轉儲功能下載hprof文件,經過platform tools中的工具轉換格式後用mat打開分析。

一般找activity,查看存在對象個數,超過一個的不正常,用mat查看它的gc roots引用,找到緣由處理

4、常見的內存泄漏緣由

一、集合類

集合類添加元素後,在使用完後未及時清理集合,致使集合中的元素沒法釋放

List<Object> objectList = new ArrayList<>();        
       for (int i = 0; i < 10; i++) {
            Object o = new Object();
            objectList.add(o);
            //o對象已經沒用了,可是objectList還存在,o沒法釋放
            o = null;
        }
複製代碼

二、靜態變量

static Context mContext;
複製代碼

若是Activity被靜態變量引用,則Activity會沒法釋放,須要特別注意

三、單例

單例這種狀況咱們特別容易忽略,須要養成習慣避免泄漏

public class SingleInstanceClass {
    
   private static SingleInstanceClass instance;
   private Context mContext;
   private SingleInstanceClass(Context context) {
       mContext = context;
   }
   
   public static SingleInstanceClass getInstance(Context context) {
       if (instance == null) {
           instance = new SingleInstanceClass(context);
       }
       return instance;
   }
}
複製代碼

這種狀況下,context若是傳的是Activity就會泄漏,應該傳Application的Context

四、非靜態內部類/匿名類

非靜態內部類/匿名類會持有外部類的引用,而靜態內部類不會

這種狀況常常發生在使用Handler的狀況時,Handler被Message引用,而消息能夠延時觸發,好比延時6秒觸發。而在6秒內Activity已經銷燬了。這時Handler還不能釋放,而Handler是非靜態內部類/匿名類狀況下,則會引用Activity,這時會形成泄漏

五、小結

本文介紹了三種常見的內存問題,6種內存分析和檢測的工具,內存抖動、內存泄漏的常見處理方式,以及Android中多種常見的因爲編碼不規範形成內存泄漏的緣由。實際生產中,內存問題很是隱蔽難以發現,最好是配合線上線下處理,線下使用如leakCanary這種優秀工具檢測,線上經過對重點模塊內存,oom率,gc次數等進行回傳,針對部分用戶進行內存快照回傳自動化分析監控等操做,來持續地以較低成本進行內存優化。

相關文章
相關標籤/搜索