1. 程式人生 > >LeakCanary 記憶體洩漏 監測 效能優化 簡介 原理 MD

LeakCanary 記憶體洩漏 監測 效能優化 簡介 原理 MD

Markdown版本筆記 我的GitHub首頁 我的部落格 我的微信 我的郵箱

目錄

簡單使用

A memory leak detection 記憶體洩露檢測 library for Android and Java.

A small leak will sink a great ship. -- Benjamin Franklin
千里之堤, 毀於蟻穴。 -- 《韓非子·喻老》

新增依賴:

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1' //當使用support庫時新增
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1' //釋出時用的是無任何操作的版本
}

初始化:

LeakCanary.install(application);

配置完以後,在 debug 構建的版本中,如果檢測到某個 activity 或 fragment 有記憶體洩露,LeakCanary 就會自動地顯示一個通知。

更多介紹

  • 為什麼要使用LeakCanary

    • 記憶體洩漏是一種程式設計錯誤[programming error],會導致應用程式保留對不再需要的物件的引用。因此就會導致無法回收為該物件分配[allocated]的記憶體,最終導致 OutOfMemoryError crash。
    • 例如,Android Activity 例項在呼叫 onDestroy 方法後就不再需要了,但是如果在靜態欄位中儲存了對該Activity的強引用將會阻止其被GC[garbage collected]
    • LeakCanary對一個 longer needed 的物件做了唯一標識,並找到阻止它被垃圾回收的引用鏈。
    • 當作者首次在Square公司的某款App中啟用 LeakCanary 後,他找到並修復了多個記憶體洩漏,並將 OutOfMemoryError 的崩潰率降低了94%。
  • LeakCanary是怎麼工作的

    • 通過 RefWatcher.watch() 建立了一個KeyedWeakReference to the watched object。
    • 然後在後臺執行緒檢查引用是否被清除了,如果沒有,則triggers a GC
    • 如果引用還是未被清除,則 dumps the heap 到檔案系統中的 .hprof 檔案中。
    • 在另外一個獨立的程序中啟動 HeapAnalyzerServiceHeapAnalyzer 使用 HAHA 解析 heap dump 。
    • 得益於唯一的 reference key, HeapAnalyzer 在 heap dump 中找到 KeyedWeakReference,並且定位 leaking reference。
    • HeapAnalyzer 計算到 GC roots 的最短強引用路徑,並確定是否有洩露。如果有的話,建立導致洩露的引用鏈。
    • 計算結果傳遞到 APP 程序中的 DisplayLeakService 中, 並以通知的形式展示出來。
  • 如何修復記憶體洩漏

    要修復某個記憶體洩漏,您需要檢視該鏈並查詢導致洩漏的那個引用,即在洩漏時哪個引用本應該被清除的。LeakCanary以紅色下劃線突出顯示可能導致洩漏的引用。

  • 如何複製 leak trace 資訊

    可以通過 logcat 或通過 Leaks App 的選單複製·

  • Android SDK可能導致洩漏嗎

    是。 在AOSP以及製造商實現中,已經存在許多已知的記憶體洩漏。 當發生這樣的洩漏時,作為應用程式開發人員,您幾乎無法解決此問題。
    出於這個原因,LeakCanary 有一個內建的已知Android漏洞列表可供忽略:AndroidExcludedRefs.java。

  • 如何通過 leak trace 挖掘洩漏資訊

    有時 leak trace 是不夠的,您需要使用 MATYourKit 挖掘 heap dump。 以下是在堆轉儲中找到洩漏例項的方法:

    • 查詢 com.squareup.leakcanary.KeyedWeakReference 的所有例項
    • 對於其中的每一個,請檢視 key 欄位。
    • 找到 key 欄位等於 LeakCanary 報告的 the reference key 的 KeyedWeakReference
    • 找到的那個 KeyedWeakReference 的 referent 欄位就是您記憶體洩漏的物件。
    • 此後,問題就掌握在你手中。A good start 是檢視 GC Roots的最短路徑(除了弱引用)。
  • 如何修復構建錯誤

    • 如果leakcan-android不在Android Studio的 external libraries 列表中,但是 leakcanary-analyzer 和 leakcanary-watcher 卻存在在那裡:嘗試做一個 Clean Build。 如果仍然存在問題,請嘗試通過命令列構建。
    • error: package com.squareup.leakcanary does not exist: 如果您有其他 build types 而不是 debug 和 release,則還需要為這些構建型別新增特定的依賴項(xxxCompile)。
  • LeakCanary添加了多少個方法

    • 如果您使用ProGuard,答案為9或0。
    • LeakCanary 只應在除錯版本中使用,並一定要在釋出版本中禁用。我們為您的釋出版本提供了一個特殊的空依賴項:leakcanary-android-no-op
    • LeakCanary 的完整版本更大,絕不應在您的 release 版本中釋出。
  • 為什麼叫 LeakCanary

    LeakCanary這個名稱是參考 煤礦中的金絲雀[canary in a coal mine],因為LeakCanary是一個用於通過提前預警危險[advance warning of a danger]來檢測風險[detect risks]的哨兵[sentinel]

  • Instant Run可能觸發無效的 leaks

    啟用Android Studio的即時執行功能可能會導致LeakCanary報告無效的記憶體洩漏。 請參閱 Android Issue Tracker 上的問題#37967114(https://issuetracker.google.com/issues/37967114)。

  • 我知道我有洩漏,為什麼通知不顯示

    你是否 attached to a debugger? LeakCanary在除錯時忽略洩漏檢測以避免誤報。

自定義 LeakCanary

  • 如何觀察具有生命週期的物件

    在您的應用程式中,您可能有其他具有生命週期的物件,例如Fragment,Service,Dagger元件等。可以使用RefWatcher來監視應該進行垃圾回收的例項:refWatcher.watch(schrodingerCat);

  • 使用 no-op 依賴

    release 版本的leakcanary-android-no-op依賴項僅包含LeakCanary和RefWatcher類。如果您要自定義LeakCanary,您需要確保自定義僅出現在 debug 版本中,因為它可能會引用leakcanary-android-no-op依賴項中不存在的類。

  • 自定義圖示和標籤

    DisplayLeakActivity附帶了一個預設圖示和標籤,您可以通過在應用中提供R.mipmap.leak_canary_iconR.string.leak_canary_display_activity_label來更改它:

  • install 方法的預設邏輯

public static RefWatcher install(Application application) {
   return refWatcher(application)
      .listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
  • 不監測特定的Activity
    預設情況下,LeakCanary會監視所有的Activity。 您可以自定義 installation steps 以執行不同的操作,例如忽略某種型別Activity的洩漏:
RefWatcher refWatcher = LeakCanary.refWatcher(this)
      .watchActivities(false)
      .buildAndInstall();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
   @Override
   public void onActivityDestroyed(Activity activity) {
      if (activity instanceof IgnoreActivity) {
         return;
      }
      refWatcher.watch(activity);
   }
   //...
});
  • 在執行時開啟和關閉 LeakCanary
refWatcher = LeakCanary.refWatcher(this)
      .heapDumper(getHeapDumper()) //在執行時開啟和關閉LeakCanary
      .buildAndInstall();
public TogglableHeapDumper getHeapDumper() {
    if (heapDumper == null) {
        LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this);
        AndroidHeapDumper defaultDumper = new AndroidHeapDumper(this, leakDirectoryProvider);
        heapDumper = new TogglableHeapDumper(defaultDumper);
    }
    return heapDumper;
}
public class TogglableHeapDumper implements HeapDumper {
    private final HeapDumper defaultDumper;
    private boolean enabled = true;

    public TogglableHeapDumper(HeapDumper defaultDumper) {
        this.defaultDumper = defaultDumper;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public File dumpHeap() {
        return enabled ? defaultDumper.dumpHeap() : HeapDumper.RETRY_LATER;
    }
}
MyApplication.app().getHeapDumper().setEnabled(false);

測試案例

Application

public class MyApplication extends Application {
    private RefWatcher refWatcher;
    private static MyApplication app;
    private TogglableHeapDumper heapDumper;

    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            Log.i("bqt", "此程序是專用於LeakCanary進行堆分析用的。您不應該在此程序中初始化您的應用。");
            return;
        }

        refWatcher = LeakCanary.refWatcher(this)
                .watchActivities(true)  //預設為true,會監視所有Activity,你可以設定為false然後再指定要監測的Activity
                .watchFragments(true) //預設為true,會監視 native Fragment,如果添加了support依賴,則也會監視support中的Fragment
                .watchDelay(1, TimeUnit.SECONDS) //設定應該等待多長時間,直到它檢查跟蹤物件是否已被垃圾回收
                .maxStoredHeapDumps(7) //設定LeakCanary最多可以儲存的 heap dumps 個數,預設為7
                .excludedRefs(getExcludedRefs()) //忽略特定的引用,這個垃圾東西設定後總是不生效
                .heapDumper(getHeapDumper()) //在執行時開啟和關閉LeakCanary
                //.listenerServiceClass() //可以更改預設行為以將 leak trace 和 heap dump 上載到您選擇的伺服器。
                .buildAndInstall();
        app = this;
    }

    private ExcludedRefs getExcludedRefs() {
        return AndroidExcludedRefs.createAppDefaults()//經過大量測試,我感覺TMD完全忽略不了Activity和Fragment中記憶體洩漏
                .instanceField("com.bqt.test.Single", "imageView") //類名,欄位名
                .staticField("com.bqt.test.StaticLeakActivity", "bitmap") //類名,靜態欄位名
                .clazz("com.bqt.test.StaticLeakActivity") //忽略提供的類名的所有子類的所有欄位和靜態欄位
                .thread("Thread-10086") //忽略指定的執行緒,一般主執行緒名為【main】,子執行緒名為【Thread-整數】
                .build(); //忽略的引用如果又通過watch手動監測了,則仍會監測其記憶體洩漏情況
    }

    public static MyApplication app() {
        return app;
    }

    public RefWatcher getRefWatcher() {
        return refWatcher;
    }

    public TogglableHeapDumper getHeapDumper() {
        if (heapDumper == null) {
            LeakDirectoryProvider leakDirectoryProvider = LeakCanaryInternals.getLeakDirectoryProvider(this);
            AndroidHeapDumper defaultDumper = new AndroidHeapDumper(this, leakDirectoryProvider);
            heapDumper = new TogglableHeapDumper(defaultDumper);
        }
        return heapDumper;
    }
}

MainActivity

public class MainActivity extends FragmentActivity implements AdapterView.OnItemClickListener {
    private FrameLayout frameLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ListView listView = new ListView(this);
        String[] array = {"靜態成員導致的記憶體洩漏",
                "單例導致的記憶體洩漏:Fragment",
                "禁用 LeakCanary",
                "",};
        listView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
        listView.setOnItemClickListener(this);
        frameLayout = new FrameLayout(this);
        frameLayout.setId(R.id.fragment_id);
        listView.addFooterView(frameLayout);
        setContentView(listView);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        switch (position) {
            case 0:
                startActivity(new Intent(this, StaticLeakActivity.class));
                break;
            case 1:
                getSupportFragmentManager().beginTransaction()
                        .add(frameLayout.getId(), new SingleLeakFragment(), "SingleLeakFragment")
                        .commit();
                break;
            case 2:
                MyApplication.app().getHeapDumper().setEnabled(false);
                break;
            default:
                break;
        }
    }
}

靜態成員導致的記憶體洩漏

public class StaticLeakActivity extends Activity {
    private static Bitmap bitmap;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ImageView imageView = new ImageView(this);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
        imageView.setImageBitmap(bitmap);
        setContentView(imageView);
    }
}


相關資訊:

* com.bqt.test.StaticLeakActivity has leaked:
* InputMethodManager$ControlledInputConnectionWrapper.!(mParentInputMethodManager)!
* ↳ InputMethodManager.!(mLastSrvView)!
* ↳ PhoneWindow$DecorView.mContext
* ↳ StaticLeakActivity
* Reference Key: 7f96d2f1-bf17-47e2-84ad-cd5976d72766
* Device: HUAWEI HONOR PLK-UL00 PLK-UL00
* Android Version: 6.0 API: 23 LeakCanary: 1.6.1 26145bf
* Durations: watch=1007ms, gc=149ms, heap dump=1840ms, analysis=6567ms

單例導致的記憶體洩漏

public class SingleLeakFragment extends Fragment {
    private ImageView imageView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        imageView = new ImageView(getContext());
        return imageView;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        imageView.setImageResource(R.drawable.icon);
        Single.SINGLETON.setImageView(imageView);//單例中引用View同樣會導致Activity記憶體洩漏
    }
}
public enum Single {
    @SuppressLint("StaticFieldLeak")
    SINGLETON; //定義一個列舉的元素,它就代表了Single的一個例項
    private ImageView imageView;

    public void setImageView(ImageView imageView) {
        this.imageView = imageView;
    }
}


相關資訊:

* com.bqt.test.SingleLeakFragment has leaked:
* InputMethodManager$ControlledInputConnectionWrapper.!(mParentInputMethodManager)!
* ↳ InputMethodManager.!(mLastSrvView)!
* ↳ ListView.mOnItemClickListener
* ↳ MainActivity.mFragments
* ↳ FragmentController.mHost
* ↳ FragmentActivity$HostCallbacks.mFragmentManager
* ↳ FragmentManagerImpl.mAdded
* ↳ ArrayList.array
* ↳ array Object[].[0]
* ↳ SingleLeakFragment
* Reference Key: 4877bf10-596c-440f-b69c-5d239f670944
* Device: HUAWEI HONOR PLK-UL00 PLK-UL00
* Android Version: 6.0 API: 23 LeakCanary: 1.6.1 26145bf
* Durations: watch=17245ms, gc=138ms, heap dump=1675ms, analysis=8159ms

2018-10-2

相關推薦

LeakCanary 記憶體洩漏 監測 效能優化 簡介 原理 MD

Markdown版本筆記 我的GitHub首頁 我的部落格 我的微信 我的郵箱 目錄 簡單使用 A memory leak detection 記憶體洩露檢測 library for Android and Java. A small leak

LeakCanary 內存泄漏 監測 性能優化 簡介 原理

tco aosp 多少 工作 play 發布 MIP 主線程 per LeakCanary 內存泄漏 監測 性能優化 簡介 原理 GitHub:https://github.com/square/leakcanary Demo地址:https://github.com/

007 LeakCanary 記憶體洩漏原理完全解析

LeakCanary 的工作原理是什麼?跟我一起揭開它的神祕面紗。 一、 什麼是LeakCanary LeakCanary 是大名鼎鼎的 square 公司開源的記憶體洩漏檢測工具。目前上大部分App在開發測試階段都會接入此工具用於檢測潛在的記憶體洩漏問題,做的好一點的可能會搭建一個伺服器用於儲存各個裝置

Hive效能優化簡介 (順便介紹了效能工具--ANALYZE)

ANALYZE關鍵字可以收集數值統計資訊。 加速查詢,直接從統計資訊中拿,而不會再啟動mapreduce去查詢。 用desc命令去查統計資訊。     Hive效能優化包含以下點: partition table 這是最好的優化,比如用年月日,部門

OpenCL入門:(三:GPU記憶體結構和效能優化)

#include <iostream> #include <CL/cl.h> #include <cassert> #include <windows.h> #include <ctime> using namespace std; #defin

Unity lua記憶體洩漏效能檢測

上週UWA發表了一片博文Lua效能優化—Lua記憶體優化作者分享了在unity中lua使用的不少乾貨,文中提到兩個lua的小外掛,一個是記憶體檢查工具Snapshot,一個是效能分析工具LuaProfiler。 正好上週筆者也涉獵到了這方面的實踐上,作為記錄我

《嵌入式Linux記憶體使用與效能優化》筆記

這本書有兩個關切點:系統記憶體(使用者層)和效能優化。 這本書和Brendan Gregg的《Systems Performance》相比,無論是技術層次還是更高的理論都有較大差距。但是這不影響,快速花點時間簡單過一遍。 然後在對《Systems Performance》進行詳細的學習。 由於Ubuntu測

Android記憶體洩漏監控和優化技巧總結

前言對於Android平臺的應用程式來說,記憶體優化一直是個熱門話題,與傳統PC應

Java記憶體模型及效能優化及Java垃圾回收

一、JVM記憶體模型 首先介紹下Java程式具體執行的過程:Java原始碼檔案(.java字尾)會被Java編譯器編譯為位元組碼檔案(.class字尾);由JVM中的類載入器載入各個類的位元組碼檔案,載入完畢之後,交由JVM執行引擎執行;在整個程式執行過程中,JVM會用一段

嵌入式Linux記憶體使用與效能優化

1.  怎樣檢視系統當前可用記憶體 ? 答:使用 free 命令即可。如下圖所示: 說明: buffers: 主要用來給Linux系統中塊裝置做緩衝區(把分散的寫操作集中進行,減少對磁碟或者Flash裝置的寫次數,提高系統性能) cached:用來緩衝開啟的檔案(把從

Android記憶體管理機制和記憶體洩漏分析及優化

Android中的記憶體管理機制 分配機制 Android為每個程序分配記憶體的時候,採用了彈性的分配方式,也就是剛開始並不會一下分配很多記憶體給每個程序,而是給每一個程序分配一個“夠用”的量。這個量是根據每一個裝置實際的實體記憶體大小來決定的。隨著應用

JVM的堆記憶體洩漏排查-效能測試

JVM異常說明 https://testerhome.com/articles/24259 一文中已介紹了,JVM每個執行時區域——程式計數器 、Java虛擬機器棧、本地方法棧、Java堆、方法區、直接記憶體發生OutOfMemoryError的不同原因和不同錯誤資訊。 Java堆,是執行緒共享記憶體,幾乎

Tomcat效能優化及JVM記憶體工作原理

Java效能優化原則:程式碼運算效能、記憶體回收、應用配置(影響Java程式主要原因是垃圾回收,下面會重點介紹這方面) 程式碼層優化:避免過多迴圈巢狀、呼叫和複雜邏輯。 Tomcat調優主要內容如下: 1、增加最大連線數 2、調整工作模式 3、啟用gzip壓縮

android效能優化——記憶體洩漏

在專案初期階段或者業務邏輯很簡單的時候對於app效能之一塊沒有太多感覺,但是隨著專案版本的迭代和專案業務邏輯越來越大,越來越複雜的時候,就會逐漸感覺到app效能的重要性,所以在專案初期階段時,就要有app效能這一意識,也便於專案後期的版本迭代和業務擴充套件;這裡所提到的效能優化問題是:記憶體洩漏

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

文章目錄 介紹 什麼是記憶體洩露 android中導致記憶體洩漏的主要幾種情況 1.單例模式 2.使用非靜態內部類 3.使用非同步事件處理機制Handler 4.使用靜態

Andorid效能優化(三) 之 如何定位記憶體洩漏

1 定位記憶體洩漏工具 正所謂工欲善其事,必先利其器。定位記憶體洩漏,可以藉助目前比較流行的一些工具來幫助發現和定位問題,下面我們就來看看這些工具。 1.1 Memory Profiler Android Studio 3.0 採用全新的Android Profiler視窗取代&nb

Andorid效能優化(二) 之 記憶體洩漏場景介紹

1 相關概念 1.1 記憶體洩漏 記憶體洩漏是指程式在向系統申請分配記憶體空間後,也就是說new了物件後,在使用完畢後沒有對其進行釋放。結果導致一直佔據該記憶體單元。簡單的說,在C/C++語言中,如果向堆中分配了記憶體(new了物件)後,沒有對其進行釋放掉(沒有delete物件),那就是

記憶體洩漏與排查流程——安卓效能優化

前言 記憶體洩漏可以說是安卓開發中常遇到的問題,追溯和排查其問題根源是進階的程式猿必須具備的一項技能。小盆友今天便與大家分享一下這方面的一些見解,如有理解錯誤或是不同見解,可以於評論區留言我們進行討論,如果喜歡給個贊鼓勵下吧。 篇幅較長,可以通過目錄尋找自己所需瞭解的吧 目錄 1、JAVA記

Linux效能優化-記憶體基本原理

目錄 記憶體對映 虛擬記憶體佈局 記憶體分配和回收 檢視記憶體使用情況 buffer和cache 測試Buffers和Cached 參考   記憶體對映 Linux核心給每個程序都提供了一個獨立的虛擬地址空間,並且這個地址空間是連續的,這樣程序就

Android 效能優化記憶體洩漏檢測以及記憶體優化(中)

Android 記憶體洩漏檢測   通過上篇部落格我們瞭解了 Android JVM/ART 記憶體的相關知識和洩漏的原因,再來歸類一下記憶體洩漏的源頭,這裡我們簡單將其歸為一下三類:自身編碼引起由專案開發人員自身的編碼造成;第三方程式碼引起這裡的第三