吐血整理!究极深刻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....

相关文章
相关标签/搜索