一、内存优化介绍
1.背景介绍
- 内存是大问题但缺乏关注
- 压实骆驼的最后一个稻草(堆栈溢出)
2.内存问题
- 内存抖动:锯齿状、GC导致卡顿
- 内存泄露:可用内存减少、频繁GC
- 内存溢出:OOM,程序异常
二、优化工具选择
1.工具选择
Memory Profiler
-
实时图标展示应用内存使用量
-
识别内存泄露、抖动等
-
提供捕获堆转储、强制GC以及跟踪内存分配的能力
总结
-
方便直观
-
线下平时使用
Memory Analyzer
- 强大的Java Heap 分析工具,查找内存泄露及内存占用
- 生成整体报告、分析问题等
- 线下深入使用
LeakCanary
- 自动内存泄露检测
- https://github.com/square/leakcanary
- 线下集成
三、Android 内存管理机制
一、Java 内存管理机制
Java 内存分配
- 方法区:存储类信息,常量、静态变量等,如 public static final 的一些变量、常量。所有线程共享
- 虚拟机栈:存储局部变量和操作数栈的(为Java方法服务的)
- 本地方法栈:(为native 方法服务的)
- 堆:几块内存块中最大的内存。所有线程共享,对象的内存分配实际上都是在堆上分配的。在虚拟机栈上分配的都是引用,会指向在堆中创建的真正的对象。是GC主要作用的一块区域。常说的内存泄露也是发送在堆上的。
- 程序计数器:存储当前线程执行方法执行到第几行
Java 内存回收算法
标记-清除算法
-
标记出所有需要回收的对象
-
统一回收所有被标记的对象
缺点
-
标记和清除效率不高
-
产生大量不连续的内存碎片
复制算法
- 将内存划分为大小相同的两块
- 一块内存用完之后复制存活对象到另一块
- 清理另一块内存
优缺点 - 实现简单,运行高效(相比标记-清除算法)
- 浪费一半空间,代价大
标记-整理算法
- 标记过程与 "标记-清除" 算法一样
- 存活对象王一端进行移动
- 清理其余内存
优点 - 避免标记-清除导致的内存碎片
- 避免复制算法的空间浪费
分代收集算法
- 结合多种收集算法优势
- 新生代对象存活率低,使用复制算法
- 老年代对象存活率高,使用标记-整理算法
二、Android内存管理机制
- 内存弹性分配,分配值与最大值受具体设备影响
- OOM场景:设备内存真正不足、可用内存不足(设备内存不足以分配给当前应用)
Dalvik 与 ART 区别
- Dalvik 仅固定一种回收算法
- Art 回收算法可以运行期选择回收算法(5.0之后都是Art)
- Art 具备内存整理能力,减少内存空洞
Low Memory Killer 机制
针对所有进程来说的,当手机内存不足的情况下,Low Memory Killer 机制会针对所有进程进行一个回收,按照进程分配 优先级顺序,找低优先进程进行回收。同时也会考虑回收收益。例如回收一个进程是30兆还是300兆
进程分类
- 前台进程
- 可见进程
- 服务进程
- 后台进程
- 空进程
四、内存抖动解决实战
内存抖动介绍
定义:内存频繁分配和回收导致内存不稳定
表现:频繁GC、内存曲线呈锯齿状
危害:导致卡顿、OOM
内存抖动导致OOM
- 频繁创建对象,导致内存不足及碎片(不连续)
- 不连续的内存片无法被分配,导致OOM
内存抖动解决实战
使用Memory Profiler 初步排查
使用Memory Profiler 或Cpu Profiler 结合代码排查
内存抖动解决技巧
- 找循环或者频繁调用的地方
五、内存泄露解决实战
内存泄露介绍
定义:内存中存在已经没有用的对象
表现:内存抖动、可用内存逐渐减少
危害;内存不足、GC频繁、OOM
内存泄露解决实战
Memory Analyzer
- https://www.eclipse.org/mat/downloads.php
- 转换命令:hprof-conv 原文件路径 转换后文件路径
总结
- 使用Memory Profiler 初步观察(可用内存逐渐减少时,可以断定有内存泄露)
- 通过Memory Analyzer 结合代码确认
六、全面理解MAT
七、ARTHook 优雅检测不合理图片
Bitmap 内存模型
- Api10 之前Bitmap 自身在Dalvik Heap 中,像素在native (有一个坏处,java 层内存回收后,native 层不知道)
- Api10 之后像素也被放在Dalvik Heap 中
- Api 26之后像素在Native(增加了java 向native 层的通知机制)
获取Bitmap 占用内存
- getByteCount
- 宽 x 高 x 一像素占用内存
常规检测不合理图片方式
背景:图片对内存优化至关重要、图片宽高大于控件宽高
实现:继承ImageView ,覆写实现计算大小
缺点:侵入性强、不通用
ARTHook 实战
介绍
挂钩,将额外的代码钩住原有方法,修改执行逻辑
- 运行时插桩
- 性能分析
Epic
- Epic 是一个虚拟机层面、以Java Method 为粒度的运行时Hook 框架
- 支持Android 4.0- 9.0
- https://github.com/tiann/epic
使用
- compile 'me.weishu:epic:0.3.6'
- 继承XC_MethodHook ,实现相应逻辑
- 注入Hook:DexposedBridge.findAandHookMethod
优点
无侵入性
通用性强
兼容问题大,开源方案不能带到线上环境
线上内存监控方案
常规方案
方案一
- 设定场景线上Dump : Debug.dumpHprofData()
(假设线上app 以使用单个程序最大占用内存的80%时,进行下线上内存Dump )
方案一总结
- Dump 文件太大(Dump 文件大小跟程序使用时长有关),和对象数正相关,可裁剪
- 上传失败率高,分析困难
- 配合一定策略,有一定效果
**
方案二
- LeakCanary 带到线上
- 预设泄露怀疑点
- 发现泄露回传
方案二总结
- 不适合所有情况,必须预设怀疑点
- 分析比较耗时、也容易OOM
LeakCanary原理
- 监控生命周期,onDestroy 添加RefWatcher 检测
- 二次确认断定发生内存泄露
- 分析泄露,找引用链
- 由监控组件 + 分析组件组成
LeakCanary 定制
- 预设怀疑点 ---> 自动找怀疑点
- 分析泄露链路慢 ----> 分析Retain size(占用内存) 大的对象
- 分析OOM ----> 对象裁剪,不全部加载到内存
线上监控完整方案
- 待机内存、重点模块内存、OOM率
- 整体及重点模块GC次数、GC 时间
- 增强的LeakCanary 自动化内存泄露分析