主頁 > 軟體設計 > 【架構筆記】Android 記憶體泄漏知識點匯總

【架構筆記】Android 記憶體泄漏知識點匯總

2021-10-16 08:38:25 軟體設計

檢測記憶體是否泄漏非常簡單,只要在任意位置呼叫 Debug.dumpHprofData(file) 即可,通過拿到 hprof 檔案進行分析就可以知道哪里產生了泄漏,但 dump 的程序會 suspend 所有的 java 執行緒,導致用戶界面無回應,所以又不能隨意 dump,為了能找到合理的 dump 時機,leakCanary 就采用預判的方式,在 onDestroy 中先檢測一下當前 Activity 是否存在泄漏的風險,如果有這種情況,就開始 dump,需要注意的是,在 onDestroy 做檢測僅僅只是預判,一種時機,并不能斷定真的發生了泄漏,真正的泄漏需要通過分析 hprof 檔案才能知曉, ?

hprof 是由 JVM TI Agent HPROF 生成的一種二進制檔案,檔案格式可以查看 Binary Dump Format:

一、如何預判記憶體泄漏

  • 主動檢測法
  • 閾值檢測法

1、主動檢測法

  • Activity 的檢測預判
  • Service 的檢測預判
  • Bitmap 大圖的檢測預判

1、Activity 的檢測預判 LeakCanary 中對 Activity 的預判是在 onDestroy 生命周期中通過弱參考佇列來持有當前 Activity 參考,如果在主動觸發 gc 之后,泄漏物件集合中仍然能找到該參考實體,則說明發生了記憶體泄漏,就開始 dump ?

2、Service 的檢測預判 LeakCanary 對 Service 的記憶體泄漏檢測時機,是 hook 監聽 ActivityThread 的 stopService,然后記錄這個 binder 到弱參考集合中,然后代理 AMS 的 serviceDoneExecuting 方法,通過 binder 在弱參考集合中去移除,移除成功的話,說明發生了記憶體泄漏,就開始 dump ?

3、Bitmap 大圖檢測預判 Bitmap 不像 Activity、Service 這種,能夠通過生命周期主動監測當前是否有記憶體泄漏的可能,他一般是在 Activity、Service 發生泄漏 dump 的時候,順便檢測一下 Bitmap ,在 Koom 中,Bitmap 大圖檢測是分析 hprof 中是否有超過 Bitmap 設定的閾值 size (width * height) ?

2、閾值檢測法

閾值檢測法的代表框架是 Koom,他拋棄了 LeakCanary 的實時檢測性,采用定時輪訓檢測當前記憶體是否在不斷累加,增長達到一定次數(可自己配置)時會進行 dump hprof,這種方式會犧牲一定的時效性,但對于應用到線上的 Koom 的框架,他完全不需要這么高的時效性 ?

二、如何分析記憶體泄漏

分析工具代表:

  • MAT
  • Android Studio
  • HaHa
    • Matrix
    • LeakCanary 1.x
  • shark
    • Liko
    • Koom
    • LeakCanary 2.x

1、MAT

MAT 工具下載可點擊鏈接 ,Android 生成的 dump 需要做一下轉換才能被 MAT 識別,轉換指令:

hprof-conv <hprof 檔案> <新生成的檔案>

eg:

hprof-conv android.hprof mat.hprof

hprof-conv 跟 adb 在同一個檔案夾下,配置了 adb 命令的可以直接用這個命令執行, ?

MAT 查記憶體泄漏會有點費勁,畢竟是個 java 通用工具,并不會指明告訴你是哪個 Activity 發生了泄漏,但可以分析個大概, ?

一般泄漏的都是比較大的實體:

在這里插入圖片描述

點擊類名進入查看: ?

在這里插入圖片描述

ActivityLeakMaker 占用了近 190944 byte 的記憶體空間,并且參考鏈里面有 Activity 相關的內容,切回代碼來看問題,原來是靜態變數持有了 Activity 實體導致:

在這里插入圖片描述

2、Android Studio

Android Studio 的 Profiler 工具支持 hprof 的決議,并且很智能的提示當前 leak 了哪些物件,打開方式很簡單,將 hprof 檔案拖拽至 as,然后雙擊 hprof 檔案即可:

在這里插入圖片描述

我們可以很直觀的看到,當前 LeakedActivity 和 ReportFragment 發生了泄漏, ?

如果我們的需求僅僅只是在開發階段進行記憶體泄漏檢測的話,并且又不想接入 LeakCanary(因為有時候想除錯下自己模塊的代碼,其他模塊經常報記憶體泄漏,凍結當前執行緒,很影響除錯),那么我們可以在應用里面埋個彩蛋,比如單擊 5 次版本號,然后呼叫 Debug.dumpHprofData ,然后將 hprof 檔案匯出到 as 進行分析,這就將原本可能會進行數次 dump 的程序,改成了自己需要去檢測的時候再去 dump, ?

3、HaHa

在 LeakCanary 的第一版的時候,是采用的 Haha 庫來分析泄漏參考鏈,但由于后面新出的 Shark,比 HaHa 快 8 倍,并且記憶體占用還要少 10 倍,但查找泄漏路徑的大致步驟與 Shark 無異,故此文就不分析 HaHa 了, ?

4、Shark

Shark 是 square 團隊開發的一款全新的分析 hprof 檔案的工具,其官方宣布比 Android Studio 用于 memory profiler 的核心庫 perflib 要快 8 倍并且記憶體占用少 10 倍,更加適合手機端的分析工具,其目的就是提供快速決議hprof檔案和分析快照的能力,并找出真正的泄漏物件以及物件到GcRoot 的最短參考路徑鏈,以便幫助開發者更加直觀的找出泄漏的真正原因, – 參考自《LeakCanary2.0決議》

看了下 Koom 分析參考鏈的程序,大致可以分為以下幾個步驟:

  • 分析 hprof 檔案,獲取鏡像所有的 instance 實體
  • 遍歷所有的實體,判斷這個實體與各個 Detectors 是否有存在泄漏,如果有,則記錄 objectId 到集合
  • 根據 objectId 集合獲取各個泄漏實體參考鏈,分析出 gcRoot,并遍歷 gcRoot 下的參考路徑

這個地方重點在于如何找到泄漏的 objectId,因為找到 objectId,即可找到泄漏參考鏈,在分析 hprof 的時候我們可以拿到 dump 時的記憶體實體,那么,我們可以根據這個實體來判斷是否泄漏,例如:

  • Activity : 判斷實體是否是 android.app.Activity 的子類,并且 mFinished 或 mDestroyed 是否為 true (Activity 關閉時該值會為 true),因為 Activity 不泄露的話肯定是會被釋放,所以,不可能存在于 dump 的實體中,有就是發生了泄漏
  • Bitmap : 獲取實體的類名稱是否為 android.graphics.Bitmap,如果是的話,則獲取實體的 mWidth 和 mHeight 實體變數,計算兩者的乘積是否超過閾值,是的話,也判定為泄漏
  • … (更多判斷可以看 analysis 目錄的各個 Detector)

Shark 根據 objectId 分析出的參考鏈路徑:

   ┬───
   │ GC Root: Local variable in native code
   │
   ├─ android.os.HandlerThread instance
   │    Leaking: UNKNOWN
   │    ↓ HandlerThread.contextClassLoader
   │                    ~~~~~~~~~~~~~~~~~~
   ├─ dalvik.system.PathClassLoader instance
   │    Leaking: UNKNOWN
   │    ↓ PathClassLoader.runtimeInternalObjects
   │                      ~~~~~~~~~~~~~~~~~~~~~~
   ├─ java.lang.Object[] array
   │    Leaking: UNKNOWN
   │    ↓ Object[].[197]
   │               ~~~~~
   ├─ com.kwai.koom.demo.leaked.ActivityLeakMaker$LeakedActivity class
   │    Leaking: UNKNOWN
   │    ↓ static ActivityLeakMaker$LeakedActivity.uselessObjectList
   │                                              ~~~~~~~~~~~~~~~~~
   ├─ java.util.ArrayList instance
   │    Leaking: UNKNOWN
   │    ↓ ArrayList.elementData
   │                ~~~~~~~~~~~
   ├─ java.lang.Object[] array
   │    Leaking: UNKNOWN
   │    ↓ Object[].[0]
   │               ~~~
   ╰→ com.kwai.koom.demo.leaked.ActivityLeakMaker$LeakedActivity instance
  ?     Leaking: YES (This is the leaking object), Signature: 39f4102649e5d3a5be12db591c2e5f68a1c0d2e9

三、如何應用于線上

1、解決 dump 凍結問題

由于 dump hprof 會暫停所有 java 執行緒問題,致使 LeakCanary 只能應用于線下檢測,但 Koom 和 Liko 另辟蹊徑,采用 linux 的 copy-on-write 機制,從當前的主執行緒 fork 出一個子行程,然后在子行程進行 dump 分析,對于用戶所在的行程不會有任何感知, ?

這個地方會有個坑,就是在 fork 子行程的時候 dump hprof,由于 dump 前會先 suspend 所有的 java 執行緒,等所有執行緒都掛起來了,才會進行真正的 dump,由于 copy-on-write 機制,子行程也會將父行程中的 threadList 也拷貝過來,但由于 threadList 中的 java 執行緒活動在父行程,子行程是無法掛起父行程中的執行緒的,然后就會一直處于等待中, ?

為了解決這個問題,Koom 和 Liko 采用欺騙的方式,在 fork 子行程之前,先將父行程中的 threadList 全部設定為 suspend 狀態,然后 fork 子行程,子行程在 dump 的時候發現 threadList 都為掛起狀態了,就立馬開始 dump hprof,然后父行程在 fork 操作之后,立馬 resume 恢復回 threadList 的狀態 ?

2、解決混淆問題

Shark 支持混淆反決議,思路也很簡單,決議 mapping.txt 檔案,每次讀取一行,只決議類和欄位:

  • 類特征 :行尾為 : 冒號結尾,然后根據 -> 作為 index 分割,左邊的為原類名,右邊的為混淆類名
  • 欄位特征:行尾不為 : 冒號結尾,并且不包含 (括號(帶括號的為方法),即為欄位特征,根據 -> 作為 index 分割,左邊為原欄位名,右邊的為混淆欄位名

將混淆類名、欄位名作為 key,原類名、原欄位名作為 value 存入 map 集合,在分析出記憶體泄漏的參考路徑類時,將類名和欄位名都通過這個 map 集合去拿到原始類名和欄位名即可,即完成混淆后的反決議 ?

leakCanary 內部是寫死的 mapping 檔案為 leakCanaryObfuscationMapping.txt,如果打開該檔案失敗,則不做參考鏈反決議:
在這里插入圖片描述

也即意味著,如果想 LeakCanary 支持混淆反決議,只需要將自己的 mapping 檔案重命名為 leakCanaryObfuscationMapping.txt,然后放入 asset 目錄即可

對于 Koom 的混淆反決議,Koom 并沒有做,但我們可以自己去加這塊代碼:

private boolean buildIndex() {
    ...
    try {
      // 新增 ---------- start
      InputStream is =  KGlobalConfig.getApplication().getResources().getAssets().open("mapping.txt");
      ProguardMapping mapping = new ProguardMappingReader(is).readProguardMapping();
     //  新增 ---------- end

      heapGraph = HprofHeapGraph.Companion.indexHprof(hprof, mapping,
              kotlin.collections.SetsKt.setOf(gcRoots));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return true;
 }         

將 mapping.txt 檔案放到 asset 目錄即可,如下是混淆與混淆反決議的參考鏈的對比:

在這里插入圖片描述

3、泄漏兜底

在預判記憶體泄漏發生時,我們可以將 Activity 中參考到的 Bitmap、DrawingCache 等進行主動釋放,以此來降低泄漏的影響面,做法是,在 Activity onDestory 時候從 view 的 rootview 開始,遞回釋放所有子 view 涉及的圖片、背景、DrawingCache、監聽器等等資源,讓 Activity 成為一個不占資源的空殼,泄露了也不會導致圖片資源被持有,eg:

...
    Drawable d = iv.getDrawable();
if (d != null) {
    d.setCallback(null);
}        
iv.setImageDrawable(null);
...
...

但這一點對于閾值檢測法的 Koom 來說,沒辦法做到,因為他拿不到 onDestroy 時的 Activity 實體,但也不要緊,我們可以將兜底操作做成通用操作,不管他泄漏與不泄露,都做 view 相關參考的卸載,

四、總結:

整體下來,分析個記憶體泄漏其實并不難,難就難在我們平時并沒有養成好的習慣,對于參考的傳遞考慮的不周全,但我們可以加強自身的編碼習慣,盡量減少專案中的泄漏問題

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/317874.html

標籤:其他

上一篇:資料轉發程序

下一篇:為什么需要使用http請求頭設定限制?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more