一、前言
我们知道,Android系统检测到app有不再使用对象时,就会进行内存回收相关的工作。
尽管Android检测无用对象、回收内存的方法在不断改进,
但在目前所有的Android版本中,进行上述工作时,系统仍需要短暂地停止app的运行。
在大多数情况下,系统进行内存回收的行为是无法被用户察觉到的。
然而,如果应用分配内存的速度大于系统回收的速度,
那么app进程的正常运行可能就回受到影响。
毕竟,系统必须回收到足够的供app需要的内存,才会恢复处于暂停状态的app。
在这种情况下,app就可能出现掉帧、卡顿等现象。
在更严重的情况下,如果出现了内存泄露的问题,那么系统中就可能堆积无法释放的内存,
使得系统必须更加频繁地进行内存回收,从而降低系统的性能。
甚至在极端条件下,系统不得不杀死部分正在后台运行的app进程。
于是用户将后台应用移到前台时,却发现应用无故重启,这显然带来了较差的用户体验。
由此可见,内存对于app而言,是极其关键的性能指标。
目前,分析app内存的工具有很多,
本文主要记录一下Android Studio内置的内存分析工具Memory Profiler。
二、基本介绍
Memory Profiler是Android Profiler的一个组件, 用于帮助分析内存泄露和内存抖动的问题。
当PC连接Android L以上的设备时,该工具才能够正常使用。
Memory Profiler的功能包括:
展示应用内存使用情况的实时图像、抓取内存的dump信息、强制垃圾回收及追踪内存分配。
2.1 开启步骤
打开Memory Profiler的步骤为:
1、 依次点击Android Studio的View → Tool Windows → Android Profiler,
或直接点击工具栏Android Profiler对应的图标;
2、 PC连接Android终端后,在Android Profiler对应的区域选择接的设备和需要监控的进程:
3、 点击Android Profiler界面中MEMORY区域的任意位置,即可开启Memory Profiler,如下图所示:
需要注意的是,如果PC连接Android 7.1以下的设备时,有些关键数据可能无法被Android Profiler统计,
此时Android Profiler会显示如下信息:
这时我们需要依次点击Android Studio的Run → Edit Configurations → Profiling 按键,选中app后点击Enabled advanced profiling,
如下图所示:
为了支持该功能,要求app对应的gradle版本必须在2.4以上。
2.2 界面介绍
打开Memory Profiler后,主界面如下所示(为了方便,这里直接盗取Android技术文档中的图):
其中:
标注1对应的按键用于强制内存回收。
标注2对应的按键用于抓取进程内存的dump信息。
标注3对应的按键用于记录内存的分配信息(连接Android 7.1及以下才会有此按键)。
初次点击时,对应统计的开始时间点;再次点击时,对应统计的结束时间点。
进程在两个时间点之间的内存分配信息,将被Memory Profiler记录和分析。
标注4对应的区域用于缩放时间轴。
标注5对应的按键用于显示实时的内存数据。
标注6对应的区域用于记录事件发生的时间点及大致持续的时间(例如activity状态改变、用户操作界面等事件)。
标注7对应的区域用于显示内存使用情况对应的时间轴(与标注6结合,就可以看出各事件带来的内存变化情况)。
需要说明的是,标注7对应区域显示的内容包括:
不同类型内存占用情况对应的图像;
分配对象数量对应的短画线;
内存回收事件发生的时机。
2.3 统计的数据类型及含义
Memory Profiler主要根据Android系统提供的信息,
统计app独自占用内存,即不统计app与系统或其它app共有的内存。
Memory Profiler统计内存的种类如下图所示:
如上图所示,其中:
Java表示Java代码或Kotlin代码分配的内存;
Native表示C或C++代码分配的内存(即使App没有native层,调用framework代码时,也有可能触发分配native内存);
Graphics表示图像相关缓存队列占用的内存;
Stack表示native和java占用的栈内存;
Code表示代码、资源文件、库文件等占用的内存;
Others表示无法明确分类的内存;
Allocated表示Java或Kotlin分配对象的数量(Android8.0以下时,仅统计Memory Profiler启动后,进程再分配的对象数量;
8.0以上时,由于系统内置了统计工具,Memory Profiler可以得到整个app启动后分配对象的数量)。
三、基本用法
对Memory Profiler有了基本的了解后,我们来看看它的基本用法。
3.1 查看内存分配情况
Memory Profiler可以查看两个时间点之间的内存分配情况,包括:
对象的类型、占用内存的大小、栈信息等。
连接8.0以上的设备时,Memory Profiler还可以显示对象被回收的时间。
PC连接8.0以上的设备时,在内存统计的时间线上,直接点击和拖动就可以选择观察区域;
连接低版本的设备时,则需要点击Record Memory allocations按键(2.2小结介绍的标注3)选择观察区域。
选定观察区域后, Memory Profiler就可以统计这段时间内app分配内存的情况:
从图中可以看出,Memory Profiler可以显示分配对象的类名;
点击类后,会在Instance View显示具体的对象;
点击具体对象后,会在Call back区域显示调用栈。
点击调用栈信息后,就会跳转到具体的代码。
3.2 查看内存占用情况
点击2.2小结介绍的标注2,即可抓取点击后一段时间内app占用内存的dump信息。
通过dump信息,我们可以看到app当前仍存在于内存中的对象。
结合代码,我们可以分析是否有本应被析构却仍存活的泄露对象。
与统计内存分配信息一样,内存占用信息同样会显示对象的类型、数量、占用内存的大小、引用关系等。
如下图所示:
图中Alloc Count表示堆中分配对象的数量;
Shallow Size表示对象使用Java内存的大小,单位为byte;
Retained Size表示对象占用的实际内存大小,大于等于Shallow Size;
7.0及以上版本的设备,还会显示对象占用的Native Size。
点击具体的对象时,也会显示Instance View。
此时,Instance View显示的信息变多了,包括:
Depth表示当前对象到任一GC root的最短跳数;
Shallow Size、Retained Size的含义与前文一致。
同样,7.0及以上版本的设备,还会显示对象占用的Native Size。
从图中可以看出,Instance View不会显示栈信息。
如果想获得栈信息的话,必须先点击Record Memory allocations按键。
四、使用示例
利用Memory Profiler,我分析了一下某反病毒引擎SDK的内存占用情况。
随便写了个demo,继承SDK后启动app,内存占用情况如下图所示:
然后,通过操作UI初始化SDK,发现稳定后内存占用情况如下图所示:
比对前后两图,不考虑界面UI变化消耗的内存,
可以看出:内存增加的主体部分来自于Native函数,其它类型的内存变化几乎可以忽略。
这是因为该SDK初始化时,最主要的工作是在Native层加载底层的so文件。
接下来,我们在demo里调用SDK的接口,批量扫描样本,统计内存消耗情况如下图所示:
从图中可以看出app消耗的内存飙升到了104M,
与初始化后的内存相比,其中增加主要是code和native类型的内存。
根据2.3小结的描述,我们知道code类型统计的是app进程需要的资源文件、库文件等,
因此这部分内存主要是SDK中引擎加载病毒库等消耗掉的内存;
而Native内存的消耗,应该也是由于引擎扫描文件导致的。
按照3.1小结的描述,我们查看了一下批量扫描这段时间内,
app进程内存分配的情况,如下图所示:
容易看出,在这段时间内,除去native内存外,整个app分配内存并不大,
且按照对象占用内存的大小排序,排在前列的都是基本数据类型。
因此,这段时间内app进程的内存应该是比较正常的。
我们进行GC操作,内存情况如下图所示:
发现内存几乎和扫描前一致,按照3.2小结进行dump分析,没有发现泄露对象。
因此,可以确认SDK不存在内存泄露的问题(dump信息含有sdk的隐私,这里就不再附图了)。
五、总结
至此,我们已经介绍了Android Memory Profiler工具的基本情况,
并简单列举了该工具的使用方式。
个人感觉,不同于LeakCanary,
Android Memory Profiler更侧重于宏观场景下的内存分析。
————————————————
版权声明:本文为CSDN博主「ZhangJianIsAStark」的原创文章,遵循 CC 4.0 BY 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gaugamela/article/details/79027538