内存优化

一. 前言

内存问题在 APP 中是个大问题,常见的问题有内存抖动、内存泄漏和内存溢出。

二. 常用工具

线下工具介绍:

  1. Memory Profiler
    Android Studio 自带的一个工具,用实时图表展示应用内存使用量,方便用来识别内存抖动、内存泄漏等,还提供捕获堆转储、强制 GC 以及跟踪内存分配的能力。


    Memory Profiler

    可以看到一共分为三块:CPU、MEMORY、NETWORK,我们主要分析MEMORY这一块。
    点击MEMORY这一块可以放大,


    MEMORY

    图中标注1的地方就是GC,如果点击一下这个按钮就会强制进行一个GC。
    图中标注2的地方是堆转储,就是将内存中的信息转成一个文件。
    图中标注3的地方是某个时间片内的内存使用情况。

    图中标注4的地方是到当前时间的内存使用情况。

现在我们来点击标注2的位置来dump一个文件:

左边的classname是类的名字,右边有Allocations表示分配了多少对象,Nativesize,ShallowSize表示自己的大小,RetainedSize表示支配的大小。

接下来我们看一个Bitmap:

点击之后,右边显示的是创建的对象,点击其中一个对象。
右下角出来一个Reference,可以看到它的一个使用情况。

再看一个Progressbar:

点击右下角的地方可以直接跳到代码中使用的位置。

  1. Memory Analyzer(MAT)
    强大的 Java Heap 分析工具,查找内存泄漏以及内存占用,生成整体报告分析问题等。
    下载地址: https://www.eclipse.org/mat/downloads.php
    (1)open overview pane是一个概览信息
    第一行有size、classes、objects、ClassLoader,还有Unreachable Objects Histogram(可以被回收的对象,但是仍然在内存当中)

(2)histogram 直方图


这个条目里面详细的列出了某个详细的class有多少实例,以及某个实例的Shallow Heap和Retained Heap。
右键可以看到两个选项:
with outgoing references:这个类引用到了那些类
with incoming references:这个类被那些类所引用
一般用下面的。

(3)dominator_tree

可以清楚的看到某个具体的对象

(4)OQL

检索数据库

(5)thread_overview

详细的线程信息

(6)Top Consumers

在做降低内存的时候比较有帮助

(7)Leak Suspects

泄漏疑点
  1. LeakCanary
    自动内存泄漏检测。
    地址: https://github.com/square/leakcanary
    缺点:虽然使用了 IdleHandler 与多线程,但是 dumphprof 的 SuspendAllThread 的特性依然会导致应用卡顿。

三. 内存问题

3.1 内存抖动

  1. 表现:
    使用 Memory Profile 工具检测,内存曲线呈锯齿状。
  2. 定义:
    内存频繁分配和回收导致内存不稳定。
  3. 危害:
    导致卡顿、OOM。
  4. 为什么内存抖动会导致 OOM?
    频繁创建对象,导致内存不足及碎片,不连续的内存碎片无法被分配,导致 OOM。
  5. 解决方法
    使用 Memory Profiler 或 CPU Profiler,点击"record",查看当前内存分配情况,然后结合代码排查。
  6. 解决技巧
    找循环或频繁调用的地方。

3.2 内存泄漏

  1. 表现:
    可用内存逐渐变少。
  2. 定义:
    内存中存在已经没有用的对象。
  3. 危害:
    内存不足、OOM。
  4. 解决方法
    使用 Memory Profiler 初步观察,然后点击“Dump Java Heap”生成 .hprof 文件,通过 "hprof-conv 原文件路径转换后文件路径" 命令进行转换,然后使用 MAT 结合代码确认问题。
    具体实战:
    (1)使用 AndroidProfiler 的 MEMORY 工具
    运行程序,对每一个页面进行内存分析检查。首先,反复打开关闭页面 5 次,然后收到 GC(点击 Profile Memory 左上角的垃圾桶图标),如果此时 total 内存还没有恢复到之前的数值,则可能发生了内存泄漏。此时,再点击 Profile Memory 左上角的垃圾桶图标旁的 heap dump 按钮查看当前的内存堆栈情况,选择按包名查找,找到当前测试的 Activity,如果引用了多个实例,则表明发生了内存泄漏。
    (2)使用 MAT 定位泄漏位置
    打开 Overview 界面,最常用的就是 Histogram 和 Dominator Tree。
    Dominator Tree:支配树,按对象大小降序列出对象和其所引用的对象,注重引用关系分析。选择 Group by package,找到当前要检测的类(或者使用顶部的 Regex 直接搜索),查看它的 Object 数目是否正确,如果多了,则判断发生了内存泄漏。然后,右击该类,选择 Merge Shortest Paths to GC Root 中的 exclude all phantom/weak/soft etc.references 选项来查看该类的 GC 强引用链。最后,通过引用链即可看到最终强引用该类的对象。
    Histogram:直方图注重量的分析。使用方式与 Dominator Tree 类似。
    (3)对比 hprof 文件,检测出复杂情况下的内存泄漏
    通过对比方式:在 Navigation History 下面选择想要对比的 dominator_tree/histogram,右击选择 Add to Compare Basket,然后在 Compare Basket 一栏中点击红色感叹号(Compare the results)生成对比表格(Compared Tables),在顶部 Regex 输入要检测的类,查看引用关系或对象数量去进行分析即可。
    针对于 Historam 的快速对比方式:直接选择 Histogram上方的 Compare to another Heap Dump 选择要比较的 hprof 文件的 Historam 即可。
  5. 常见的内存泄漏场景
    (1)资源对象没有关闭造成的内存泄漏;
    解决方法:当资源对象不再使用时,应该立即调用它的close() 函数,将其关闭,然后再置为 null。
    (2)注册没有取消造成的内存泄漏;
    (3)集合中对象没清理造成的内存泄漏;
    (4)非静态内部类的静态实例;
    原因:
    首先,非静态内部类默认会持有外部类的引用,然后又使用了该非静态内部类创建了一个静态的实例,该静态实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
    解决方法:
    一种是将非静态内部类改成静态内部类,第二种就是将内部类抽取出来,封装成一个单例,如果需要使用 Context,尽量使用 Application Context。
    (5)Handler 引起的内存泄漏
    原因:
    当Handler发送消息的时候,这个Message还没有处理完成,这个message以及发送Message的对象handler都将一直被线程所持有,其中Handler是TLS变量,也就是Handler的生命周期和Activity的生命周期是不一致的,另外创建Handler的时候用的是匿名内部类,所以会持有Activity,当页面销毁的时候还会有handler存在,也就是Activity不能被回收。
    解决方法:
    (1)将Handler声明为静态的
    (2)通过弱引用的方式引入Activity。

(6)Webview造成的内存泄漏
Webview在解析网页的时候,会申请native堆内存,用于保存页面的元素。
解决方法:
将webview所处的activity放在一个单独的进程当中,在检测到应用内存占用过大的时候,调用 android.os.Process.killProcess(android.os.Process.myPid());主动杀掉进程。

3.3 内存溢出

  1. 表现:
    APP崩溃并报Out Of Memory异常。
  2. 定义:
    分配的内存被用光了。

四. Bitmap 优化

4.1 Bitmap 内存模型

(1)API 10 之前 Bitmap 自身在 Dalvik Heap 中,像素在 Native(好处:这些像素不占用 Java 层的内存,缺点:Java 层的 bitmap 已经被回收掉了,但是 native 层不知道)
(2)API 10 之后像素也被放在 Dalvik Heap 中;
(3)API 26 之后像素在 Native(但是在 Java 层回收 bitmap 时,会通知 native 层回收像素)
(4)获取 Bitmap 占用内存
动态计算获得:getByteCount
静态获得:宽 * 高 * 一像素占用内存

4.2 优化方式

  1. 统一图片库
    图片内存优化的前提是收拢图片的调用,这样我们可以做整体的控制策略,比如低端机使用 565 格式,而且需要进一步将所有 Bitmap.createBitmap、BitmapFactory 相关的接口也一并收拢。

  2. 对 bitmap 图片大小进行优化
    (1)继承 ImageView,复写实现计算大小
    缺点:侵入性强、不通用
    (2)ARTHook
    运行时插桩,修改大小

  3. 重复图片复用

五. 线上内存监控

上面优化都是线下进行的,但是对于线上的应用就需要做到监控。

  1. 方案
    当超过 APP 最大内存 80% 时,执行Debug.dumpHprofData() 下载文件,然后回传文件到服务器,之后用 MAT 手动分析。
  2. 缺点
    (1)dump 文件太大,和对象数正相关,可裁剪;
    (2)上传失败率高、分析困难;
    (3)配合一定策略,有一定效果;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335