吐血整理!究極深刻Android內存優化(三)


前言:

前面已經給你們整理了Android內存優化的五個知識點,錯過的老鐵能夠補一下,java

吐血整理!究極深刻Android內存優化(一)吐血整理!究極深刻Android內存優化(二)
android

今天咱們來擼一擼第六點和第七點,廢話很少說,直接開整!git


                                                  

6、內存優化演進

一、自動化測試階段

內存達到閾值後自動觸發 Hprof Dump,將獲得的 Hprof 存檔後由人工經過 MAT 進行分析。github

二、LeakCanary

檢測和分析報告都在一塊兒,批量自動化測試和過後分析都不太方便。web

三、使用基於 LeakCannary 的改進版 ResourceCanary

Matrix => ResourceCanary 實現原理算法

主要功能

目前,它的主要功能有 三個部分,以下所示:shell

一、分離 檢測和分析 兩部分流程

自動化測試由測試平臺進行,分析則由監控平臺的服務端離線完成,最後再通知相關開發解決問題。數據庫

二、裁剪 Hprof文件,以下降 傳輸 Hprof 文件與後臺存儲 Hprof 文件的開銷

獲取 須要的類和對象相關的字符串 信息便可,其它數據均可以在客戶端裁剪,通常能 Hprof 大小會減少至原來的 1/10 左右。json

三、增長重複 Bitmap 對象檢測

方便經過減小冗餘 Bitmap 的數量,以下降內存消耗。瀏覽器

四、小結

在研發階段須要不斷實現 更多的工具和組件,以此係統化地提高自動化程度,以最終 提高發現問題的效率

7、內存優化工具

除了經常使用的內存分析工具 Memory Profiler、MAT、LeakCanary 以外,還有一些其它的內存分析工具,下面我將一一爲你們進行介紹。

一、top

top 命令是 Linux 下經常使用的性能分析工具,可以 實時顯示系統中各個進程的資源佔用情況,相似於 Windows 的任務管理器。top 命令提供了 實時的對系統處理器的狀態監視。它將 顯示系統中 CPU 最「敏感」的任務列表。該命令能夠按 CPU使用、內存使用和執行時間 對任務進行排序

接下來,咱們輸入如下命令查看top命令的用法:

quchao@quchaodeMacBook-Pro ~ % adb shell top --help
usage: top [-Hbq] [-k FIELD,] [-o FIELD,] [-s SORT] [-n NUMBER] [-d SECONDS] [-p PID,] [-u USER,]

Show process activity in real time.

-H	Show threads
-k	Fallback sort FIELDS (default -S,-%CPU,-ETIME,-PID)
-o	Show FIELDS (def PID,USER,PR,NI,VIRT,RES,SHR,S,%CPU,%MEM,TIME+,CMDLINE)
-O	Add FIELDS (replacing PR,NI,VIRT,RES,SHR,S from default)
-s	Sort by field number (1-X, default 9)
-b	Batch mode (no tty)
-d	Delay SECONDS between each cycle (default 3)
-n	Exit after NUMBER iterations
-p	Show these PIDs
-u	Show these USERs
-q	Quiet (no header lines)

Cursor LEFT/RIGHT to change sort, UP/DOWN move list, space to force
update, R to reverse sort, Q to exit.複製代碼

這裏使用 top 僅顯示一次進程信息,以便來說解進程信息中各字段的含義。

image

總體的統計信息區

前四行 是當前系統狀況 總體的統計信息區。下面咱們看每一行信息的具體意義。

第一行:Tasks — 任務(進程)

具體信息說明以下所示:

系統如今共有 729 個進程,其中處於 運行中 的有 1 個,715 個在 休眠(sleep)stoped 狀態的有0個,zombie 狀態(殭屍)的有 8 個。

第二行:內存狀態

具體信息以下所示:

  • 1)、5847124k total:物理內存總量(5.8GB)
  • 2)、5758016k used:使用中的內存總量(5.7GB)
  • 3)、89108k free:空閒內存總量(89MB)
  • 4)、112428k buffers:緩存的內存量 (112M)

第三行:swap交換分區信息

具體信息說明以下所示:

  • 1)、2621436k total:交換區總量(2.6GB)
  • 2)、612572k used:使用的交換區總量(612MB)
  • 3)、2008864k free:空閒交換區總量(2GB)
  • 4)、2657696k cached:緩衝的交換區總量(2.6GB)

第四行:cpu狀態信息

具體屬性說明以下所示:

  • 1)、800% cpu:8核 CPU
  • 2)、39% user:39% CPU被用戶進程使用
  • 3)、0% nice:優先值爲負的進程佔 0%
  • 4)、42% sys — 內核空間佔用 CPU 的百分比爲 42%
  • 5)、712% idle:除 IO 等待時間之外的其它等待時間爲 712%
  • 6)、0% iow:IO 等待時間佔 0%
  • 7)、0% irq:硬中斷時間佔 0%
  • 8)、6% sirq - 軟中斷時間佔 0%

對於內存監控,在 top 裏咱們要時刻監控 第三行 swap 交換分區的 used,若是這個數值在不斷的變化,說明內核在不斷進行內存和 swap 的數據交換,這是真正的內存不夠用了。

進程(任務)的狀態監控

第五行及如下,就是各進程(任務)的狀態監控,項目列信息說明以下所示:

  • 1)、PID:進程 id
  • 2)、USER:進程全部者
  • 3)、PR:進程優先級
  • 4)、NI:nice 值。負值表示高優先級,正值表示低優先級
  • 5)、VIRT:進程使用的虛擬內存總量。VIRT = SWAP + RES
  • 6)、RES:進程使用的、未被換出的物理內存大小。RES = CODE + DATA
  • 7)、SHR:共享內存大小
  • 8)、S:進程狀態。D = 不可中斷的睡眠狀態、R = 運行、 S = 睡眠、T = 跟蹤 / 中止、Z = 殭屍進程
  • 9)、%CPU — 上次更新到如今的 CPU 時間佔用百分比
  • 10)、%MEM:進程使用的物理內存百分比
  • 11)、TIME+:進程使用的 CPU 時間總計,單位 1/100秒
  • 12)、ARGS:進程名稱(命令名 / 命令行)

從上圖中能夠看到,第一行的就是 Awesome-WanAndroid 這個應用的進程,它的進程名稱爲 json.chao.com.w+,PID 爲 23104,進程全部者 USER 爲 u0_a714,進程優先級 PR 爲 10,nice 置 NI 爲 -10。進程使用的虛擬內存總量 VIRT 爲 4.3GB,進程使用的、未被換出的物理內存大小 RES 爲138M,共享內存大小 SHR 爲 66M,進程狀態 S 是睡眠狀態,上次更新到如今的 CPU 時間佔用百分比 %CPU 爲 21.2。進程使用的物理內存百分比 %MEM 爲 2.4%,進程使用的 CPU 時間 TIME+ 爲 1:47.58 / 100小時。

二、dumpsys meminfo

四大內存指標

在講解 dumpsys meminfo 命令以前,咱們必須先了解下 Android 中最重要的 四大內存指標 的概念,以下表所示:

內存指標 英文全稱 含義 等價
USS Unique Set Size 物理內存 進程獨佔的內存
PSS Proportional Set Size 物理內存 PSS = USS + 按比例包含共享庫
RSS Resident Set Size 物理內存 RSS= USS+ 包含共享庫
VSS Virtual Set Size 虛擬內存 VSS= RSS+ 未分配實際物理內存

從上可知,它們之間內存的大小關係爲 VSS >= RSS >= PSS >= USS

RSS 與 PSS 類似,也包含進程共享內存,但比較麻煩的是 RSS 並無把共享內存大小全都平分到使用共享的進程頭上,以致於全部進程的 RSS 相加會超過物理內存不少。而 VSS 是虛擬地址,它的上限與進程的可訪問地址空間有關,和當前進程的內存使用關係並不大。好比有不少的 map 內存也被算在其中,咱們都知道,file 的 map 內存對應的多是一個文件或硬盤,或者某個奇怪的設備,它與進程使用內存並無多少關係。

PSS、USS 最大的不一樣在於 「共享內存「(好比兩個 App 使用 MMAP 方式打開同一個文件,那麼打開文件而使用的這部份內存就是共享的),USS不包含進程間共享的內存,而PSS包含。這也形成了USS由於缺乏共享內存,全部進程的USS相加要小於物理內存大小的緣由。

最先的時候官方就推薦使用 PSS 曲線圖來衡量 App 的物理內存佔用,而 Android 4.4 以後才加入 USS。可是 PSS,有個很大的問題,就是 」共享內存「,考慮一種狀況,若是 A 進程與 B 進程都會使用一個共享 SO 庫,那麼 So 庫中初始化所用掉的那部份內存就會平分到 A 與 B 的頭上。可是 A 是在 B 以後啓動的,那麼對於 B 的 PSS 曲線而言,在 A 啓動的那一刻,即便 B 沒有作任何事情,也會出現一個比較大的階梯狀下滑,這會給用曲線圖分析軟件內存的行爲形成致命的麻煩

USS 雖然沒有這個問題,可是因爲 Dalvik 虛擬機申請內存牽扯到 GC 時延和多種 GC 策略,這些都會影響到曲線的異常波動。例如異步 GC 是 Android 4.0 以上系統很重要的特性,可是 GC 何時結束?曲線何時」下降「?就 沒法預計 了。還有 GC 策略,何時開始增長 Dalvik 虛擬機的預申請內存大小(Dalvik 啓動時是有一個標稱的 start 內存大小,它是爲 Java 代碼運行時預留的,避免 Java 運行時再申請而形成卡頓),可是這個 預申請大小是動態變化的,這一點也會 形成 USS 忽大忽小

dumpsys meminfo 命令解析

瞭解完 Android 內存的性能指標以後,下面咱們便來講說 dumpsys meminfo 這個命令的用法,首先咱們輸入 adb shell dumpsys meminfo -h 查看它的幫助文檔:

quchao@quchaodeMacBook-Pro ~ % adb shell dumpsys meminfo -h
meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]
-a: include all available information for each process.
-d: include dalvik details.
-c: dump in a compact machine-parseable representation.
-s: dump only summary of application memory usage.
-S: dump also SwapPss.
--oom: only show processes organized by oom adj.
--local: only collect details locally, don't call process. --package: interpret process arg as package, dumping all processes that have loaded that package. --checkin: dump data for a checkin If [process] is specified it can be the name or pid of a specific process to dump.複製代碼

接着,咱們之間輸入adb shell dumpsys meminfo命令:

quchao@quchaodeMacBook-Pro ~ % adb shell dumpsys meminfo
Applications Memory Usage (in Kilobytes):
Uptime: 257501238 Realtime: 257501238

// 根據進程PSS佔用值從大到小排序
Total PSS by process:
    308,049K: com.tencent.mm (pid 3760 / activities)
    225,081K: system (pid 2088)
    189,038K: com.android.systemui (pid 2297 / activities)
    188,877K: com.miui.home (pid 2672 / activities)
    176,665K: com.plan.kot32.tomatotime (pid 22744 / activities)
    175,231K: json.chao.com.wanandroid (pid 23104 / activities)
    126,918K: com.tencent.mobileqq (pid 23741)
    ...

// 以oom來劃分,會詳細列舉全部的類別的進程
Total PSS by OOM adjustment:
    432,013K: Native
        76,700K: surfaceflinger (pid 784)
        59,084K: android.hardware.camera.provider@2.4-service (pid 743)
        26,524K: transport (pid 23418)
        25,249K: logd (pid 597)
        11,413K: media.codec (pid 1303)
        10,648K: rild (pid 1304)
        9,283K: media.extractor (pid 1297)
        ...
        
    661,294K: Persistent
        225,081K: system (pid 2088)
        189,038K: com.android.systemui (pid 2297 / activities)
        103,050K: com.xiaomi.finddevice (pid 3134)
        39,098K: com.android.phone (pid 2656)
        25,583K: com.miui.daemon (pid 3078)
        ...
        
    219,795K: Foreground
        175,231K: json.chao.com.wanandroid (pid 23104 / activities)
        44,564K: com.miui.securitycenter.remote (pid 2986)
        
    246,529K: Visible
        71,002K: com.sohu.inputmethod.sogou.xiaomi (pid 4820)
        52,305K: com.miui.miwallpaper (pid 2579)
        40,982K: com.miui.powerkeeper (pid 3218)
        24,604K: com.miui.systemAdSolution (pid 7986)
        14,198K: com.xiaomi.metoknlp (pid 3506)
        13,820K: com.miui.voiceassist:core (pid 8722)
        13,222K: com.miui.analytics (pid 8037)
        7,046K: com.miui.hybrid:entrance (pid 7922)
        5,104K: com.miui.wmsvc (pid 7887)
        4,246K: com.android.smspush (pid 8126)
        
    213,027K: Perceptible
        89,780K: com.eg.android.AlipayGphone (pid 8238)
        49,033K: com.eg.android.AlipayGphone:push (pid 8204)
        23,181K: com.android.thememanager (pid 11057)
        13,253K: com.xiaomi.joyose (pid 5558)
        10,292K: com.android.updater (pid 3488)
        9,807K: com.lbe.security.miui (pid 23060)
        9,734K: com.google.android.webview:sandboxed_process0 (pid 11150)
        7,947K: com.xiaomi.location.fused (pid 3524)
        
    308,049K: Backup
        308,049K: com.tencent.mm (pid 3760 / activities)
        
    74,250K: A Services
        59,701K: com.tencent.mm:push (pid 7234)
        9,247K: com.android.settings:remote (pid 27053)
        5,302K: com.xiaomi.drivemode (pid 27009)
        
    199,638K: Home
        188,877K: com.miui.home (pid 2672 / activities)
        10,761K: com.miui.hybrid (pid 7945)
        
    53,934K: B Services
        35,583K: com.tencent.mobileqq:MSF (pid 14119)
        6,753K: com.qualcomm.qti.autoregistration (pid 8786)
        4,086K: com.qualcomm.qti.callenhancement (pid 26958)
        3,809K: com.qualcomm.qti.StatsPollManager (pid 26993)
        3,703K: com.qualcomm.qti.smcinvokepkgmgr (pid 26976)
        
    692,588K: Cached
        176,665K: com.plan.kot32.tomatotime (pid 22744 / activities)
        126,918K: com.tencent.mobileqq (pid 23741)
        72,928K: com.tencent.mm:tools (pid 18598)
        68,208K: com.tencent.mm:sandbox (pid 27333)
        55,270K: com.tencent.mm:toolsmp (pid 18842)
        24,477K: com.android.mms (pid 27192)
        23,865K: com.xiaomi.market (pid 27825)
        ...

// 按內存的類別來進行劃分
Total PSS by category:
    957,931K: Native
    284,006K: Dalvik
    199,750K: Unknown
    193,236K: .dex mmap
    191,521K: .art mmap
    110,581K: .oat mmap
    101,472K: .so mmap
    94,984K: EGL mtrack
    87,321K: Dalvik Other
    84,924K: Gfx dev
    77,300K: GL mtrack
    64,963K: .apk mmap
    17,112K: Other mmap
    12,935K: Ashmem
     3,364K: Stack
     2,343K: .ttf mmap
     1,375K: Other dev
     1,071K: .jar mmap
        20K: Cursor
         0K: Other mtrack

// 手機總體內存使用狀況
Total RAM: 5,847,124K (status normal)
Free RAM: 3,711,324K (  692,588K cached pss + 2,428,616K cached kernel +   117,492K cached ion +   472,628K free)
Used RAM: 2,864,761K (2,408,529K used pss +   456,232K kernel)
Lost RAM:   184,330K
    ZRAM:   174,628K physical used for   625,388K in swap (2,621,436K total swap)
Tuning: 256 (large 512), oom   322,560K, restore limit   107,520K (high-end-gfx)複製代碼

根據 dumpsys meminfo 的輸出結果,可歸結爲以下表格:

劃分類型 排序指標 含義
process PSS 以進程的PSS從大到小依次排序顯示,每行顯示一個進程,通常用來作初步的競品分析
OOM adj PSS 展現當前系統內部運行的全部Android進程的內存狀態和被殺順序,越靠近下方的進程越容易被殺,排序按照一套複雜的算法,算法涵蓋了先後臺、服務或節目、可見與否、老化等
category PSS 以Dalvik/Native/.art mmap/.dex map等劃分並按降序列出各種進程的總PSS分佈狀況
total - 總內存、剩餘內存、可用內存、其餘內存

此外,爲了 查看單個 App 進程的內存信息,咱們能夠輸入以下命令:

dumpsys meminfo <pid> // 輸出指定pid的某一進程
dumpsys meminfo --package <packagename> // 輸出指定包名的進程,可能包含多個進程複製代碼

這裏咱們輸入 adb shell dumpsys meminfo 23104 這條命令,其中 23104 爲 Awesome-WanAndroid App 的 pid,結果以下所示:

quchao@quchaodeMacBook-Pro ~ % adb shell dumpsys meminfo 23104
Applications Memory Usage (in Kilobytes):
Uptime: 258375231 Realtime: 258375231

** MEMINFO in pid 23104 [json.chao.com.wanandroid] **
                Pss  Private  Private  SwapPss     Heap     Heap     Heap
                Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
Native Heap    46674    46620        0      164    80384    60559    19824
Dalvik Heap     6949     6912       16       23    12064     6032     6032
Dalvik Other     7672     7672        0        0
       Stack      108      108        0        0
      Ashmem      134      132        0        0
     Gfx dev    16036    16036        0        0
   Other dev       12        0       12        0
   .so mmap     3360      228     1084       27
  .jar mmap        8        8        0        0
  .apk mmap    28279    11328    11584        0
  .ttf mmap      295        0       80        0
  .dex mmap     7780       20     4908        0
  .oat mmap      660        0       92        0
  .art mmap     8509     8028      104       69
 Other mmap      982        8      848        0
 EGL mtrack    29388    29388        0        0
  GL mtrack    14864    14864        0        0
    Unknown     2532     2500        8       20
      TOTAL   174545   143852    18736      303    92448    66591    25856

App Summary
                   Pss(KB)
                    ------
       Java Heap:    15044
     Native Heap:    46620
            Code:    29332
           Stack:      108
        Graphics:    60288
   Private Other:    11196
          System:    11957

           TOTAL:   174545       TOTAL SWAP PSS:      303

Objects
           Views:      171         ViewRootImpl:        1
     AppContexts:        3           Activities:        1
          Assets:       18        AssetManagers:        6
   Local Binders:       32        Proxy Binders:       27
   Parcel memory:       11         Parcel count:       45
Death Recipients:        1      OpenSSL Sockets:        0
        WebViews:        0

SQL
        MEMORY_USED:      371
 PAGECACHE_OVERFLOW:       72          MALLOC_SIZE:      117

DATABASES
    pgsz     dbsz   Lookaside(b)          cache  Dbname
        4       60            109      151/32/18  /data/user/0/json.chao.com.wanandroid/databases/bugly_db_
        4       20             19         0/15/1  /data/user/0/json.chao.com.wanandroid/databases/aws_wan_android.db複製代碼

該命令輸出了 進程的內存概要,咱們應該着重關注 四個要點,下面我將一一進行講解。

一、查看 Native Heap 的 Heap Alloc 與 Dalvik Heap 的 Heap Alloc

  • 1)、Heap Alloc:表示 native 的內存佔用,若是持續上升,則可能有泄漏
  • 2)、Heap Alloc:表示 Java 層的內存佔用

二、查看 Views、Activities、AppContexts 數量變化狀況

若是 Views 與 Activities、AppContexts 持續上升,則代表有內存泄漏的風險

三、SQL 的 MEMORY_USED 與 PAGECACHE_OVERFLOW

  • 1)、MEMOERY_USED:表示數據庫使用的內存
  • 2)、PAGECACHE_OVERFLOW:表示溢出也使用的緩存,這個數值越小越好

四、查看 DATABASES 信息

  • 1)、pgsz:表示數據庫分頁大小,這裏全是 4KB
  • 2)、Lookaside(b):表示使用了多少個 Lookaside 的 slots,可理解爲內存佔用的大小
  • 3)、cache:一欄中的 151/32/18 則分別表示 分頁緩存命中次數/未命中次數/分頁緩存個數,這裏的未命中次數不該該大於命中次數

三、LeakInspector

LeakInspector 是騰訊內部的使用的 一站式內存泄漏解決方案,它是 Android 手機通過長期積累和提煉、集內存泄漏檢測、自動修復系統Bug、自動回收已泄露Activity內資源、自動分析GC鏈、白名單過濾 等功能於一體,並 深度對接研發流程、自動分析責任人並提缺陷單的全鏈路體系

那麼,LeakInspector 與 LeakCanary 又有什麼不一樣之處呢?

它們之間主要有 四個方面 的不一樣,以下所示:

1、檢測能力與原理方面不一樣

一、檢測能力

它們都支持對 Activity、Fragment 及其它自定義類的泄漏檢測,可是,LeakInspector 還 增長了 Btiamp 的檢測能力,以下所示:

  • 1)、檢測有沒有在 View 上 decode 超過該 View 尺寸的圖片,如有則上報出現問題的 Activity 及與其對應的 View id,並記錄它的個數與平均佔用內存的大小。
  • 2)、檢測圖片尺寸是否超過全部手機屏幕大小,違規則報警。

這一個部分的實現原理,咱們能夠採用 ARTHook 的方式來實現,還不清楚的朋友請再仔細看看大圖檢測的部分。

二、檢測原理

兩個工具的泄漏檢測原理都是在 onDestroy 時檢查弱引用,不一樣之處在於 LeakInspector 直接使用 WeakReference 來檢測對象是否已經被釋放,而 LeakCanary 則使用 ReferenceQueue,二者效果是同樣的。

而且針對 Activity,咱們一般都會使用 Application的 registerActivityLifecycleCallbacks 來註冊 Activity 的生命週期,以重寫 onActivityDestroyed 方法實現。可是在 Android 4.0 如下,系統並無提供這個方法,爲了不手動在每個 Activity 的 onDestroy 中去添加這份代碼,咱們可使用 反射 Instrumentation 來截獲 onDestory,以下降接入成本。代碼以下所示:

Class<?> clazz = Class.forName("android.app.ActivityThread");
Method method = clazz.getDeclaredMethod("currentActivityThread", null);
method.setAccessible(true);
sCurrentActivityThread = method.invoke(null, null);
Field field = sCurrentActivityThread.getClass().getDeclaredField("mInstumentation");
field.setAccessible(true);
field.set(sCurrentActivityThread, new MonitorInstumentation());複製代碼

2、泄漏現場處理方面不一樣

一、dump 採集

二者都能採集 dump,可是 LeakInspector 提供了回調方法,咱們能夠增長更多的自定義信息,如運行時 Log、trace、dumpsys meminfo 等信息,以輔助分析定位問題。

二、白名單定義

這裏的白名單是爲了處理一些系統引發的泄漏問題,以及一些由於 業務邏輯要開後門的情形而設置 的。分析時若是碰到白名單上標識的類,則不對這個泄漏作後續的處理。兩者的配置差別有以下兩點:

  • 1)、LeakInspector 的白名單以 XML 配置的形式存放在服務器上。

    • 優勢:跟產品甚至不一樣版本的應用綁定,咱們能夠很方便地修改相應的配置。
    • 缺點:白名單裏的類不區分系統版本一刀切。
  • 1)、而LeakCanary的白名單是直接寫死在其源碼的AndroidExcludedRefs類裏。

    • 優勢:定義很是詳細,並區分系統版本。
    • 缺點:每次修改一定得從新編譯。
  • 2)、LeakCanary 的系統白名單裏定義的類比 LeakInspector 中定義的多不少,由於它沒有自動修復系統泄漏功能。

三、自動修復系統泄漏

針對系統泄漏,LeakInspector 經過 反射自動修復 了目前碰到的一些系統泄漏,只要在 onDestory 裏面 調用 一個修復系統泄漏的方法便可。而 LeakCanary 雖然能識別系統泄漏,可是它僅僅對該類問題給出了分析,沒有提供實際可用的解決方案。

四、回收資源(Activity內存泄漏兜底處理)

若是檢測到發生了內存泄漏,LeakInspector 會對整個 Activity 的 View 進行遍歷,把圖片資源等一些佔內存的數據釋放掉,保證這次泄漏只會泄漏一個Activity的空殼,儘可能減小對內存的影響。代碼大體以下所示:

if (View instanceof ImageView) {
    // ImageView ImageButton處理
    recycleImageView(app, (ImageView) view);
} else if (view instanceof TextView) {
    // 釋放TextView、Button周邊圖片資源
    recycleTextView((TextView) view);
} else if (View instanceof ProgressBar) {
    recycleProgressBar((ProgressBar) view);
} else {
    if (view instancof android.widget.ListView) {
        recycleListView((android.widget.ListView) view);
    } else if (view instanceof android.support.v7.widget.RecyclerView) {
        recycleRecyclerView((android.support.v7.widget.RecyclerView) view);
    } else if (view instanceof FrameLayout) {
        recycleFrameLayout((FrameLayout) view);
    } else if (view instanceof LinearLayout) {
        recycleLinearLayout((LinearLayout) view);
    }
    
    if (view instanceof ViewGroup) {
        recycleViewGroup(app, (ViewGroup) view);
    }
}複製代碼

這裏以 recycleTextView 爲例,它回收資源的方式以下所示:

private static void recycleTextView(TextView tv) {
    Drawable[] ds = tv.getCompoundDrawables();
    for (Drawable d : ds) {
        if (d != null) {
            d.setCallback(null);
        }
    }
    tv.setCompoundDrawables(null, null, null, null);
    // 取消焦點,讓Editor$Blink這個Runnable再也不被post,解決內存泄漏。
    tv.setCursorVisible(false);
}複製代碼

3、後期處理不一樣

一、分析與展現

採集 dump 以後,LeakInspector 會上傳 dump 文件,並*

調用 MAT 命令行來進行分析
*,獲得此次泄漏的 GC 鏈。而 LeakCanary 則用開源組件 HAHA 來分析獲得一個 GC 鏈。可是 LeakCanary 獲得的 GC 鏈包含被 hold 住的類對象,通常都不須要用 MAT 打開 Hporf 便可解決問題。而 LeakInpsector 獲得的 GC 鏈只有類名,還須要 MAT 打開 Hprof 才能具體去定位問題,不是很方便。

二、後續跟進閉環

LeakInspector 在 dump 分析結束以後,會提交缺陷單,而且把缺陷單分配給對應類的負責人。若是發現重複的問題則更新舊單,同時具有從新打開單等狀態轉換邏輯。而 LeakCanary 僅會在通知欄提醒用戶,須要用戶本身記錄該問題並作後續處理。

4、配合自動化測試方面不一樣

LeakInspector 跟自動化測試能夠無縫結合,當自動化腳本執行中發現內存泄漏,能夠由它採集 dump 併發送到服務進行分析,最後提單,整個流程是不須要人力介入的。而 LeakCanary 則把分析結果經過通知欄告知用戶,須要人工介入才能進入下一個流程。

四、JHat

JHat 是 Oracle 推出的一款 Hprof 分析軟件,它和 MAT 並稱爲 Java 內存靜態分析利器。不一樣於 MAT 的單人界面式分析,jHat 使用多人界面式分析。它被 內置在 JDK 中,在命令行中輸入 jhat 命令可查看有沒有相應的命令。

quchao@quchaodeMacBook-Pro ~ % jhat
ERROR: No arguments supplied
Usage:  jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

    -J<flag>          Pass <flag> directly to the runtime system. For
		    example, -J-mx512m to use a maximum heap size of 512MB
    -stack false:     Turn off tracking object allocation call stack.
    -refs false:      Turn off tracking of references to objects
    -port <port>:     Set the port for the HTTP server.  Defaults to 7000
    -exclude <file>:  Specify a file that lists data members that should
		    be excluded from the reachableFrom query.
    -baseline <file>: Specify a baseline object dump.  Objects in
		    both heap dumps with the same ID and same class will
		    be marked as not being "new".
    -debug <int>:     Set debug level.
		        0:  No debug output
		        1:  Debug hprof file parsing
		        2:  Debug hprof file parsing, no server
    -version          Report version number
    -h|-help          Print this help and exit
    <file>            The file to read

For a dump file that contains multiple heap dumps,
you may specify which dump in the file
by appending "#<number>" to the file name, i.e. "foo.hprof#3".複製代碼

出現如上輸出,則代表存在 jhat 命令。它的使用很簡單,直在命令行輸入 jhat xxx.hprof 便可,以下所示:

quchao@quchaodeMacBook-Pro ~ % jhat Documents/heapdump/new-33.hprof
Snapshot read, resolving...
Resolving 408200 objects...
Chasing references, expect 81 dots.................................................................................
Eliminating duplicate references.................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.複製代碼

jHat 的執行過程是解析 Hprof 文件,而後啓動 httpsrv 服務,默認是在 7000 端口監聽 Web 客戶端連接,維護 Hprof 解析後的數據,以持續供給 Web 客戶端進行查詢操做

啓動服務器後,咱們打開 入口地址 127.0.0.1:7000 便可查看 All Classes 界面,以下圖所示:


jHat 還有兩個比較重要的功能,分別以下所示:

一、統計表

打開 127.0.0.1:7000/histo/,統計表界面以下所示:


能夠到,按 Total Size 降序 排列了全部的 Class,而且,咱們還能夠查看到每個 Class 與之對應的實例數量。

二、OQL 查詢

OQL 是一種模仿 SQL 語句的查詢語句,一般用來查詢某個類的實例數量,打開 127.0.0.1:7000/oql/ 並輸入 java.lang.String 查詢 String 實例的數量,結果以下圖所示:


JHat 比 MAT 更加靈活,且符合大型團隊安裝簡單、團隊協做的需求。可是,並不適合中小型高效溝通型團隊使用。

五、ART GC Log

GC Log 分爲 Dalvik 和 ART 的 GC 日誌,關於 Dalvik 的 GC 日誌,咱們在前篇 Android性能優化以內存優化 中已經詳細講解過了,接下來咱們說說 ART 的 GC 日誌

ART 的日誌與 Dalvik 的日誌差距很是大,除了格式不一樣以外,打印的時間也不一樣,並且,它只有在慢 GC 時纔會打印出來。下面咱們看看這條 ART GC Log:

Explicit (full) concurrent mark sweep GC freed 104710 (7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free,25MB/38MB paused 1.230ms total 67.216ms
GC產生的緣由 GC類型 採集方法 釋放的數量和佔用的空間 釋放的大對象數量和所佔用的空間 堆中空閒空間的百分比和(對象的個數)/(堆的總空間) 暫停耗時

GC 產生的緣由

GC 產生的緣由有以下九種:

  • 1)、Concurrent、Alloc、Explicit 跟 Dalvik 的基本同樣,這裏就不重複介紹了。
  • 2)、NativeAlloc:Native 內存分配時,好比爲 Bitmaps 或者 RenderScript 分配對象, 這會致使Native內存壓力,從而觸發GC
  • 3)、Background:後臺 GC,觸發是爲了給後面的內存申請預留更多空間
  • 4)、CollectorTransition:由堆轉換引發的回收,這是運行時切換 GC 而引發的。收集器轉換包括將全部對象從空閒列表空間複製到碰撞指針空間(反之亦然)。當前,收集器轉換僅在如下狀況下出現:在內存較小的設備上,App 將進程狀態從可察覺的暫停狀態變動爲可察覺的非暫停狀態(反之亦然)
  • 5)、HomogeneousSpaceCompact:齊性空間壓縮是指空閒列表到壓縮的空閒列表空間,一般發生在當 App 已經移動到可察覺的暫停進程狀態。這樣作的主要緣由是減小了內存使用並對堆內存進行碎片整理
  • 6)、DisableMovingGc:不是真正的觸發 GC 緣由,發生併發堆壓縮時,因爲使用了 GetPrimitiveArrayCritical,收集會被阻塞。通常狀況下,強烈建議不要使用 GetPrimitiveArrayCritical
  • 7)、HeapTrim:不是觸發GC緣由,可是請注意,收集會一直被阻塞,直到堆內存整理完畢

GC 類型

GC 類型有以下三種:

  • 1)、Full:與Dalvik的 FULL GC 差很少
  • 2)、Partial:跟 Dalvik 的局部 GC 差很少,策略時不包含 Zygote Heap
  • 3)、Sticky:另一種局部中的局部 GC,選擇局部的策略是上次垃圾回收後新分配的對象

GC採集的方法

GC 採集的方法有以下四種:

  • 1)、mark sweep:先記錄所有對象,而後從 GC ROOT 開始找出間接和直接的對象並標註。利用以前記錄的所有對象和標註的對象對比,其他的對象就應該須要垃圾回收了
  • 2)、concurrent mark sweep:使用 mark sweep 採集器的併發 GC
  • 3)、mark compact:在標記存活對象的時候,全部的存活對象壓縮到內存的一端,而另外一端能夠更加高效地被回收
  • 4)、semispace:在作垃圾掃描的時候,把全部引用的對象從一個空間移到另一個空間,而後直接 GC 剩餘在舊空間中的對象便可

經過 GC 日誌,咱們能夠知道 GC 的量和 它對卡頓的影響,也能夠 初步定位一些如主動調用GC、可分配的內存不足、過多使用Weak Reference 等問題。

六、Chrome Devtool

對於 HTML5 頁面而言,抓取 JavaScript 的內存須要使用 Chrome Devtools 來進行遠程調試。方式有以下兩種:

  • 1)、直接把 URL 抓取出來放到 Chrome 裏訪問。
  • 2)、用 Android H5 遠程調試。

純H5

一、手機安裝 Chrome,打開 USB 調試模式,經過 USB 連上電腦,在 Chrome 裏打開一個頁面,好比百度頁面。而後在 PC Chrome 地址欄裏訪問 Chrome://inspect,以下圖所示:


二、最後,直接點擊 Chrome 下面的 inspect 選項便可彈出開發者工具界面。以下圖所示:


默認 Hybrid H5 調試

Android 4.4 及以上系統的原生瀏覽器就是 Chrome 瀏覽器,可使用 Chrome Devtool 遠程調試 WebView,前提是須要在 App 的代碼裏把調試開關打開,以下代碼所示:

if (Build.VERSION_SDK_INT >= Build.VERSION_CODES.KITKAT && 是debug模式) {
    WebView.setWebContentsDebuggingEnabled(ture);
}
複製代碼複製代碼

打開後的調試方法跟純 H5 頁面調試方法同樣,直接在 App 中打開 H5 頁面,再到 PC Chrome 的 inpsector 頁面就能夠看到調試目標頁面。

這裏總結一下 JS 中幾種常見的內存問題點

  • 1)、closure 閉包函數
  • 2)、事件監聽
  • 3)、變量做用域使用不當,全局變量的引用致使沒法釋放
  • 4)、DOM 節點的泄漏

若想更深刻地學習 Chrome 開發者工具的使用方法,請查看 《Chrome開發者工具中文手冊》


做者:jsonchao
連接:https://juejin.im/post/5e780257f265da575209652c

後續還有Android內存優化終極篇!記得點個關注,以爲文章還不錯,評論給個666~

關注我,你就是,我baba....

相關文章
相關標籤/搜索