眼瞅着还有一个月就过年,项目也没有那么忙了,技术老大要求做一做性能优化方面的工作。
而我的任务就是把项目中的内存泄漏撸一遍,然后安排对应的人处理。
说到内存泄漏,我也算是老手了,其实在
2016
年我就写个内存泄漏方面的文章:
Android 如何有效的解决内存泄漏的问题
把内存泄漏的地方找出来
说干就干,首要的任务就是把内存泄漏的代码揪出来,我选择
leakcanary
github 地址:https://github.com/square/leakcanary/
关于 leakcanary 的介绍,集成步骤,我就不在这里展开讲述了,网上有很多文章,你们自己搜索一下。
我只啰嗦一点:
-
1、
leakcanary
2.0之后的版本集成不需要初始化
集成完成后,把项目跑起来,过一会就会报出来很多内存泄漏的日志。
在 Android Studio
logcat
过滤
LeakCanary
就会看到如下:
同时在手机桌面也会有一个 小鸟图标,点击会看到可视化页面
这里就可以看到内存泄漏的链条,分析的方式是
从下往上
的顺序,比如针对本图:
首先 MainActivity 实例发生内存泄漏
-->
再往上可以看到
MainActivity
泄漏 原因是 MainActivity 里的一个 Lambda 表达式引起的
-->
…
-->
再往上 看到
MutableLiveData
引起的
-->
…
-->
…
看到这个分析链条,我们就很清楚了,大概率是
MutableLiveData
对象引起的,再结合实际的项目代码,最后发现果然是因为
MainActivity
里的
MutableLiveData
对象没有释放。
特别要注意的是:
纵然
leakcanary
工具很牛逼,但是要想清晰的定位,然后修复内存泄漏,还是要结合实际的项目代码的。
到这里我们基本就完成了 把内存泄漏揪出来 的问题。
leakcanary 状态
在上面一部分,我贴了两个图,图中的有很清晰的对象引用链条。leakcanary 对每个对象都标明了泄漏的状态。
-
Leaking: YES 确定已经泄漏
-
Leaking: UNKNOWN 不确定是否泄漏
-
Leaking: NO 没有泄漏
我们在分析 对象引用链条的时候,要特别注意
UNKNOWN
状态,这个状态即有可能是泄漏了,也有可能是没有泄漏,这就需要我们程序要认真的分析项目代码,然后给出结论
hprof
leakcanary 在运行的时候,发现内存泄漏了,会把 Java堆快照转储到Android HPROF文件中,方便开发者分析。
如何获取 HPROF
方式一:通过 logcat 获取
在 Android Studio 的 logcat 中会输出 hprof 文件地址:
我们拿到 hprof 文件链接后,就可以通过
adb pull
命令导到电脑桌面
adb pull /storage/emulated/0/Download/leakcanary-com.cootek.crazyreader/2021-01-08_11-21-08_472.hprof ~/DeskTop
方式二:通过客户端可视化页面
点击
Share Heap Dump file
可以通过分享出去。
正文到这里其实也就结束了。在做内存泄漏排查的时候用到了 AndroidStudio Profiler 工具,里面有很多新的概念和内存指标,下面的内容就是在探究 Profiler 工具如何使用以及各种内存指标所代表的含义
如何打开 hprof 文件
方式一:
Android studio
Profiler 功能打开
这个工具显示了如下信息:
Class name
Total Count
该类的实例总数
Heap Count
所选择的堆中该类的实例的数量
Sizeof
单个实例所占空间大小(如果每个实例所占空间大小不一样则显示0)
Shallow Size
堆里所有实例大小总和(Heap Count * Sizeof)
Retained Size
该类所有实例所支配的内存大小
Instance
具体的实例
Reference Tree
所选实例的引用,以及指向该引用的引用。
Depth
GC根节点到所选实例的最短路径的深度
Shallow Size
所选实例的大小
Dominating Size
所选实例所支配的内存大小
用HPROF分析工具,可以检测到泄漏的 Activity
通过这个可以看到 本次分析有 3 出泄漏的地方,点击 第一个 ReaderActivity
可以看到详细的泄漏实例,Depth 为 13 ,代表 GC根节点到所选ReaderActivity实例的最短路径的深度是 13 。
Heap Dump
Heap Dump 是什么?
Heap Dump
也叫堆转储文件,是一个
Java
进程在某个时间点上的内存快照。
Heap Dump
是有着多种类型的。不过总体上
heap dump
在触发快照的时候都保存了
java
对象和类的信息。通常在写
heap dump
文件前会触发一次
FullGC
,所以
heap dump
文件中保存的是
FullGC
后留下的对象信息。
简单说就是:
heap dump
文件是一个二进制文件,它保存了某一时刻
JVM
堆中对象使用情况。
HeapDump
文件是指定时刻的
Java
堆栈的快照,是一种镜像文件。
Heap Dump里面有什么?
一般在
Heap Dump
文件中可以获取到(这仍然取决于heap dump文件的类型)如下信息:
-
对象信息:类、成员变量、引用值;
-
类信息:类加载器、名称、超类、静态成员;
-
Garbage Collections Roots:JVM可达的对象;
-
线程栈以及本地变量:获取快照时的线程栈信息,以及局部变量的详细信息
也就是说我们可以对上面这些内容进行分析。通常可以基于
Heap Dump
分析如下类型的问题:
-
找出内存泄漏的原因;
-
找出重复引用的jar或类;
-
分析集合的使用;
-
分析类加载器。
总而言之我们对
Heap Dump
的分析就是对应用的内存使用进行分析,从而更加合理地使用内存。
如何做一次
Heap Dump
?
在前面讲到的,
hprof
文件都是
Leakcanary
工具帮我们做的,那我们自己想要自己做一次
Heap Dump
,生成 hprof 文件又该怎么做呢?
其实 AndroidStudio 有现成的工具,只要动动手机就行了。
AndroidStudio
-->
Profiler
-->
点击 + 号
-->
选择设备
-->
选择进程
-->
点击 MEMORY
-->
点击 向下的箭头
由于生成的 hprof 文件比较大,所以解析出来比较慢,要耐心等待。
至此,我们就完成手动
Heap Dump
操作,并且生成
hprof
文件 。我们也可以点击保存按钮,把
hprof
文件保存到桌面,或者发给其他人。
如何代码触发 Heap Dump
代码其实很简单:
try {
//指定Hprof文件的名字
var path: String =
externalCacheDir?.absolutePath + File.separator + System.currentTimeMillis() + ".hprof"
Debug.dumpHprofData(path)
} catch (e: Exception) {
生成的文件在 Android/data/app包名/cache/
目录下:
如何手动触发 GC
AndroidStudio -->
Profiler -->
点击 + 号 -->
选择设备 -->
选择进程 -->
点击 MEMORY -->
点击 像垃圾桶 的图标
其实最快速的是点击 右键
Android Profiler指标
官方文档:https://developer.android.com/studio/profile/memory-profiler
内存计数中的类别如下:
Java:从 Java 或 Kotlin 代码分配的对象的内存。
Native:从 C 或 C++ 代码分配的对象的内存。
即使您的应用中不使用 C++,您也可能会看到此处使用了一些原生内存,因为即使您编写的代码采用 Java 或 Kotlin 语言,Android 框架仍使用原生内存代表您处理各种任务,如处理图像资源和其他图形。
Graphics:图形缓冲区队列为向屏幕显示像素(包括 GL
表面、GL
纹理等等)所使用的内存。(请注意,这是与 CPU 共享的内存,不是 GPU
专用内存。)
Stack:您的应用中的原生堆栈和 Java 堆栈使用的内存。这通常与您的应用运行多少线程有关。
Code:您的应用用于处理代码和资源(如 dex
字节码、经过优化或编译的 dex
代码、.so
库和字体)的内存。
Others:您的应用使用的系统不确定如何分类的内存。
Allocated:您的应用分配的 Java/Kotlin
对象数。此数字没有计入 C
或 C++
中分配的对象。
如果连接到搭载 Android 7.1
及更低版本的设备,只有在内存性能分析器连接到您运行的应用时,才开始此分配计数。因此,您开始分析之前分配的任何对象都不会被计入。但是,Android 8.0
及更高版本附带一个设备内置性能剖析工具,该工具可跟踪所有分配,因此,在 Android 8.0
及更高版本上,此数字始终表示您的应用中待处理的 Java 对象总数。
Heap Dump指标分析
只有看懂了每个指标,才能更好的分析内存,下面我们分析一下 heap Dump
指标
对象跟踪策略
为了在分析时提高应用性能,内存性能分析器在默认情况下会定期对内存分配进行采样。在运行 API 级别 26 或更高级别的设备上进行测试时,您可以使用 Allocation Tracking
下拉菜单更改此行为。可用选项如下:
- Full:捕获内存中的所有对象分配。这是 Android Studio 3.2 及更低版本中的默认行为。如果您有一个分配了大量对象的应用,可能会在分析时观察到应用的运行速度明显减慢。
- Sampled:定期对内存中的对象分配进行采样。这是默认选项,在分析时对应用性能的影响较小。在短时间内分配大量对象的应用仍可能会表现出明显的速度减慢。
- Off/None:停止跟踪应用的内存分配。
记录某一段时间的内存分配情况
Heap Dump
是一个很好用的工具,能够分析内存中所有的对象,但是也是有弊端的,Heap Dump
是全量分析,如果你想分析某一段时间内的内存增量分配情况,该怎么做呢?点击 Record
按钮.
下面用一个 gif 看看
Memory Analyzer(MAT)
Memory Analyzer
工具,简称:MAT
。
MAT
是 Eclipse 下的一个软件,专门用来分析 Java内存堆。
官方下载地址:https://www.eclipse.org/mat/
安装完成后,图标如下
不得不说,这个工具长得很丑。
MAT
可以打开 .hprof
, 但是从 AndroidStudio 里面的导出的 .hprof
文件,MAT
是不支持查看的,所以需要转化一下,Android SDK 自带了转化工具。
您可以使用 android_sdk/platform-tools/
目录中提供的 hprof-conv
工具执行此操作。运行包含两个参数(即原始 HPROF 文件和转换后 HPROF 文件的写入位置)的hprof-conv
命令。例如:
hprof-conv heap-original.hprof heap-converted.hprof
经过转化过的 .hprof
文件,MAT
就可以打开了。
快过年了,祝大家 2021 事事顺心,万事大吉。新年快乐鸭 !!