上篇说了一些性能优化的理论部分, 主要是回顾一下, 有了理论, 小平同志又讲了, 实践是检验真理的唯一标准, 对于内存泄露的问题, 现在通过 Android Studio 自带工具 Memory Monitor 检测出来性能优化的重要性不需要在强调, 但是要强调一下, 我并不是一个老司机, 嘿嘿! 没用过这个工具的, 请睁大眼睛如果你用过, 那么就不用在看这篇博客了
先看一段会发生内存泄露的代码
- public class UserManger {
- private static UserManger instance;
- private Context context;
- private UserManger(Context context) {
- this.context = context;
- }
- public static UserManger getInstance(Context context) {
- if (instance == null) {
- instance = new UserManger(context);
- }
- return instance;
- }
- }
- public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- UserManger userManger = UserManger.getInstance(this);
- }
- }
代码很简单, 就是一个单利模式泄露的场景, 我们现在的关心的不是代码本身, 而是如何将代码里面的内存泄露给找出来但是对于上面的代码发生内存泄露的原因还是有必要提一下
上篇博客说了, 内存泄漏产生的原因是: 当一个对象已经不需要再使用了, 本该被回收时, 而有另外一个正在使用的对象持有它的引用从而就导致, 对象不能被回收这种导致了本该被回收的对象不能被回收而停留在堆内存中, 就产生了内存泄漏
在上面的代码中, 发生泄露的不是 UserManger, 而是 MainActivity,UserManger 中有一个静态成员 instance, 其生命周期和应用程序的生命周期一致, 当退出应用时, 才能被销毁, 但是当 GC 准备回收 MainActivity 时, 结果呢 MainActivity 的对象 (this) 在被 UserManger 所引用, UserManger 本身又不能被干掉, 所以就发生了内存泄露
monitors.png
Memory Monitor 是 Android Monitors 中的一种, Monitors 主要包括四种, Memory Monitor ,CPU Monitor ,NetWork Monitor, GPU Monitor , 今天介绍的是 Memory Monitor , 其他的 Monitor, 在后面也准备讲
Memory Monitor 界面
Memory Monitor.png
图中水平方向是时间轴, 竖直方向是内存的分配情况
图中深蓝色的区域, 表示当前正在使用中的内存总量, 浅蓝色或者浅灰色区域, 表示空闲内存或者叫作未分配内存
内存分析的工具栏, 从上向下一共 4 个按钮, 依次是:
终止检测的开关, 没什么实质性的作用
就是手动调用 GC, 我们在抓内存前, 一定要手动点击 Initiate GC 按钮手动触发 GC, 这样抓到的内存使用情况就是不包括 Unreachable 对象的(Unreachable 指的是可以被垃圾回收器回收的对象, 但是由于没有 GC 发生, 所以没有释放, 这时抓的内存使用中的 Unreachable 就是这些对象)
(Dump Java Heap)点击可以生成一个文件(包名 + 日期 +.hprof), 可以记录摸一个时间点内, 程序内存的情况获取 hprof 文件(hprof 文件是我们使用 MAT 工具分析内存时使用的文件), 但这里直接产生的文件 MAT 还不能直接使用, 需用转换成标准的 hprof 文件可以使用 AndroidStudio 转换或者用 hprof-conv 命令转化, 网上可以查到
开始分配追踪, 第一次点击可以指定追踪内存的开始位置, 第二次点击可以结束追踪的位置这样我们截取了一段要分析的内存, 等待几秒钟 AndroidStudio 会给我们打开一个 Allocation 视图(感觉和 MAT 工具差不多, 不过 MAT 工具更加强大, 我们也可以获取 hprof 文件, 使用 MAT 来分析)
回到我们的程序, 多点击几次 GC, 看一下这个应用的内存使用情况
内存使用情况. jpg
可以看到现在已经分配的内存有 19.68M, 我把手机旋转一下, 在看
旋转后内存使用情况. png
可以看到现在的内存使用量是 21.09M, 还是一样的界面, 却多了 1.41M!!! 这很关键
接下来, 我们找一下, 哪里发生了泄露点击 Dump Java Heap, 生成快照文件 tool.test.memory.memoryleak_2016.11.13_21.38.hprof,Android Studio 自动弹出 HPROF Viewer 来分析它
快照文件分析. png
现在介绍一下 HPROF Viewer 的用法
HPROF Viewer 查看方式
左上角两个红框, 是可选列表, 分别是用来选择 Heap 区域, 和 Class View 的展示方式的.
Heap 类型分为:
App Heap -- 当前 App 使用的 Heap
Image Heap -- 磁盘上当前 App 的内存映射拷贝
Zygote Heap -- Zygote 进程 Heap(每个 App 进程都是从 Zygote 孵化出来的, 这部分基本是 framework 中的通用的类的 Heap)
Class List View -- 类列表方式
Package Tree View -- 根据包结构的树状显示
我通常点击 App heap 下面的 Classs Name 把 Heap 中所有类按照字母顺序排序, 然后按照字母顺序查找
HPROF Viewer 主要分 ABC 三大板块
板块 A: 这个应用中所有类的名字
版块 B: 左边类的所有实例
板块 C: 在选择 B 中的实例后, 这个实例的引用树
A 板块左上角列名解释
列名 | 解释 |
---|---|
Class Name | 类名,Heap 中的所有 Class |
Total Count | 内存中该类这个对象总共的数量,有的在栈中,有的在堆中 |
Heap Count | 堆内存中这个类 对象的个数 |
Sizeof | 每个该实例占用的内存大小 |
Shallow Size | 所有该类的实例占用的内存大小 |
Retained Size | 所有该类对象被释放掉,会释放多少内存 |
B 板块右上角上角列名解释
列名 | 解释 |
---|---|
Instance | 该类的实例 |
Depth | 深度, 从任一 GC Root 点到该实例的最短跳数 |
Dominating Size | 该实例可支配的内存大小 |
B 板块右上角有个 " 的按钮, 点击会进入 HPROF Analyzer 的 hprof 的分析界面:
Analyzer Tasks.png
点击 Analyzer Tasks 右边的绿色运行箭头, Android Studio 会自动的根据此 hprof 文件分析有哪些类是有内存泄漏的, 如下图所示:
下面分析一下 MainActivity 的泄露情况
MainActivity 发生内存泄露. png
在这个界面中可以直接把内存泄露可能的类找出来
一个 Activity 应该只有一个实例, 但是从 A 区域来看 total count 的值为 2,heap count 的值也为 2, 说明有一个是多余的
在 B 区域中可以看见两个 MainActivity 的实例, 点击一个看他的引用树情况
在 C 区域中可以看到 MainActivity 的实例 Context 被 UserManger 的 instance 引用了, 引用深度为 1.
在 Analyzer Tasks 区域中, 直接告诉你 Leaked Activities,MainActivity 包含其中
多方面的证据表明 MainActivity 发生了内存泄露
解决方案
- public class UserManger {
- private static UserManger instance;
- private Context context;
- private UserManger(Context context) {
- this.context = context;
- }
- public static UserManger getInstance(Context context) {
- if (instance == null) {
- if(context!=null){
- instance = new UserManger(context.getApplicationContext());
- }
- }
- return instance;
- }
- }
不要用 Activity 的 Context, 因为 Activity 随时可能被回收, 我们用 Application 的 Context,Application 的 Context 的生命周期是整个应用, 不回收也没有关系
Memory Monitor 获得内存的动态视图, Heap Viewer 显示堆内存中存储了什么, 可惜 Heap Viewer 不能显示你的数据具体分配在代码的何处, 如果还不过瘾, 想知道具体是哪些代码使用了内存, 还有一个功能是 Allocation Tracker, 用来内存分配追踪在内存图中点击途中标红的部分, 启动追踪, 再次点击就是停止追踪, 随后自动生成一个 alloc 结尾的文件, 这个文件就记录了这次追踪到的所有数据, 然后会在右上角打开一个数据面板
Allocation Tracker 启动追踪
Allocation Tracker 启动追踪. png
,
Allocation Tracker 查看方式
Allocation Tracker 查看方式
有两种查看方式, 默认是 Group by Method 方式
Group by Method: 用方法来分类我们的内存分配
Group by Allocator: 用内存分配器来分类我们的内存分配
从上图可以看出, 首先以线程对象分类, Size 是内存大小, Count 是分配了多少次内存, 点击一下线程就会查看每个线程里所有分配内存的方法
Group by Method 方式
每个线程里所有分配内存的方法. png
OK,-Memory Monitor
Group by Allocator 方式
EY%HY_B74%BUE22C6$G~CTP.png
右键可以直接跳到源码
- 扇形统计图
AQFHT$@7TYP0S_1`DU@%S%6.png
点击统计图按钮, 会生成上图, 扇形统计图是以圆心为起点, 最外层是其内存实际分配的对象, 每一个同心圆可能被分割成多个部分, 代表了其不同的子孙, 每一个同心圆代表他的一个后代, 每个分割的部分代表了某一带人有多人, 你双击某个同心圆中某个分割的部分, 会变成以你点击的那一代为圆心再向外展开
Memory Monitor 可以发现的问题
Memory Monitor 工具为监控工具, 是一种发现型或者说监控性质的工具, 比如医生的四大技能 [望闻问切],[望] 是第一步这里的 Memory Monitor 就是一种 [望] 的工具, 目前我主要用它来看下面几个内存问题:
1. 发现内存抖动的场景
2. 发现大内存对象分配的场景
3. 发现内存不断增长的场景
4. 确定卡顿问题是否因为执行了 GC 操作
案例分析
上面的第一段标记显示内存突然增加了 7M, 我们也能看的很清楚, 所以这个点我们要去定位了一下问题在哪里, 是 Bitmap 还是什么原因造成的, 第二段标记是内存抖动, 很明显在很短的时间了发生了多次的内存分配和释放而且在发生内存抖动的时候, 也能感觉到 App 的卡顿, 可以看出来是由于执行了 GC 操作造成的
内存的不断增加通过 Memory monitor 很容易看出来, 蓝色的曲线是一路高歌猛进的, 一看便知
来源: http://www.bubuko.com/infodetail-2507393.html