共计 5365 个字符,预计需要花费 14 分钟才能阅读完成。
为了帮忙开发者开发出更加轻快高效的利用,咱们在 Android Studio 3.0 以及更高版本中退出了 Android Profiler 工具,用于利用的 CPU、内存、网络和能耗剖析。
在 Android Profiler 提供的这四种性能数据中,绝大多数场景下咱们都更关怀 CPU 和内存的应用状况。本文将介绍对应的两种剖析工具 —— Memory Profiler 和 CPU Profiler。
Memory Profiler
许多开发者应用 Memory Profiler,是心愿发现和定位内存透露问题。在介绍 Memory Profile 如何解决这一问题之前,我想先明确 “ 内存透露 ” 这一概念。无论您以后是否理解内存透露,都将帮忙我更好地解释 Memory Profile 的工作原理。
内存透露
什么是内存透露?
通常咱们认为,在运行的程序中,如果一个无法访问的对象却依然占用着内存空间,即为此对象造成了内存透露。如果您应用过 C 语言或 C++ 的指针,您会很相熟这个概念。
然而在 Kotlin 和 Java 的世界中,事件有些许不同。因为这两种语言是运行在 Java 虚拟机 (JVM) 中的。在 JVM 中,有个重要的概念,就是垃圾回收 (GC)。当垃圾回收运行时,虚构机会首先辨认 GC Root。GC Root 是一个能够从堆内部拜访的对象,它能够是本地变量或运行中的线程等。虚构机会辨认所有能够从 GC Root 拜访的对象,它们将会被保留。而其余无奈从 GC root 拜访的对象,则会被认为是垃圾并回收掉。
所以,个别意义上的内存透露在 JVM 中并不存在。在 JVM 中的内存透露通常是指: 内存中含有那些再也不会被应用、然而依然可能拜访的对象。
Activity 和 Fragment 透露检测
在 Android 利用中,该当尤为警觉 Activity 和 Fragment 对象的透露,因为这两种对象通常都会占用很多内存。在 Android 3.6 中,Memory Profiler 退出了主动查看 Activity 和 Fragment 中的内存透露的性能。应用这一性能十分的简略:
- 首先,您须要在 Memory Profiler 中保留 Heap Dump,点击下图所示按钮:
- 在 Heap Dump 加载实现后,勾选 “Activity/Fragment Leaks” 选框:
此时如果有查看到 Activity 或 Fragment 的透露,就会在界面中显示进去。
Memory Profiler 通过以下几种场景来判断透露是否产生:
- 当咱们销毁了一个 Activity 的实例后,这个实例就再也不会被应用了。此时如果依然有这个 Activity 的援用,Memory Profiler 就会认为它曾经透露;
- Fragment 的实例该当与一个 Fragment Manager 相关联,如果咱们看到一个 Fragment 没有关联任何一个 Fragment Manager,而且它仍然被援用时,也能够认为有透露产生。
不过要留神的是,针对 Fragment 有个特地的状况: 如果您载入的 Heap Dump 的机会,刚好介于 Fragment 被创立和被应用的工夫之间,就会造成 Memory Profiler 误报;雷同状况也会产生在 Fragment 被缓存然而没有被复用的时候。
其余内存透露检测
Memory Profiler 也能够用于查看其余类型的透露,它提供了许多信息,用于帮忙您辨认内存透露是否产生。
当您拿到一段 Heap Dump 之后,Memory Profiler 会展现出类的列表。对于每个类,”Allocation” 这一列显示的是它的实例数量。而在它左边则顺次是 “Native Size”、”Shallow Size” 和 “Retained Size”:
这几组数据别离意味着什么呢?上面我会通过一个例子来阐明。
咱们用下图来示意某段 Heap Dump 记录的利用内存状态。留神红色的节点,在这个示例中,这个节点所代表的对象从咱们的工程中援用了 Native 对象:
这种状况不太常见,但在 Android 8.0 之后,应用 Bitmap 便可能产生此类情景,因为 Bitmap 会把像素信息存储在原生内存中来缩小 JVM 的内存压力。
先从 “Shallow Size” 讲起,这列数据其实非常简单,就是对象自身耗费的内存大小,在上图中,即为红色节点本身所占内存。
而 “Native Size” 同样也很简略,它是类对象所援用的 Native 对象 (蓝色节点) 所耗费的内存大小:
“Retained Size” 稍简单些,它是下图中所有橙色节点的大小:
因为一旦删除红色节点,其余的橙色节点都将无奈被拜访,这时候它们就会被 GC 回收掉。从这个角度上讲,它们是被红色节点所持有的,因而被命名为 “Retained Size”。
还有一个后面没有提到的数据维度。当您点击某个类名,界面中会显示这个类实例列表,这里有一列新数据 —— “Depth”:
“Depth” 是从 GC Root 达到这个实例的最短门路,图中的这些数字就是每个对象的深度 (Depth):
一个对象离 GC Root 越近,它就越有可能与 GC Root 有多条门路相连,也就越可能在垃圾回收中被保留下来。
以红色节点为例,如果从其右边来的任何一个援用被毁坏,红色节点就会变成不可拜访的状态并且被垃圾回收回收掉。而对于左边的蓝色节点来说,如果您心愿它被垃圾回收,那您须要把左右两边的门路都毁坏才行。
值得警觉的是,如果您看到某个实例的 “Depth” 为 1 的话,这意味着它间接被 GC root 援用,同时也意味着它永远不会被主动回收。
上面是一个示例 Activity,它实现了 LocationListener 接口,高亮局部代码 “requestLocationUpdates” 将会应用以后 Activity 实例来注册 locationManager。如果您遗记登记,这个 Activity 就会透露。它将永远都待在内存里,因为地位管理器是一个 GC root,而且永远都存在:
您能在 Memory Profiler 中查看这一状况。点击一个实例,Memory Profiler 将会关上一个面板来显示谁正在援用这个实例:
咱们能够看到地位管理器中的 mListener 正在援用这个 Activity。您能够更进一步,通过援用面板导航至堆的援用视图,它能够让您验证这条援用链是否是您所预期的,也能帮您了解代码中是否有透露以及哪里有透露。
CPU Profiler
和 Memory Profiler 相似,CPU Profiler 提供了从另一个角度记录和剖析利用要害性能数据的办法。
应用 CPU Profiler,首先要产生一些 CPU 的应用记录:
- 进入 Android Studio 中的 CPU Profiler 界面,在您的利用曾经部署的前提下,点击 “Record” 按钮;
- 在利用中进行您想要剖析的操作;
- 返回 CPU Profiler,点击 “Stop” 按钮。
因为最终出现的数据是基于线程组织的,所以去察看数据之前,您应该确认是否抉择了正确的线程:
咱们这里所取得的 CPU 应用记录信息,其实是一个 System Trace 实例的调用栈汇合 (下文统称 “ 调用栈 ”)。而就算是很短的 CPU 应用记录,也会蕴含巨量的信息,同时这些信息也是人无奈读懂的。所以 CPU Profiler 提供了一些工具来可视化这些数据。
Call Chart
在 CPU Profiler 界面下半部,有四个标签页,别离对应四个不同的数据图表,它们别离是: Call Chart、Flame Chart、Top Down 和 Bottom Up。其中的 Call Chart 可能是最直白的一个,它基本上就是一个调用栈的从新组织和可视化出现:
Call Chart 横轴就是工夫线,用来展现办法开始与完结的确切工夫,纵轴则自上而下展现了办法间调用和被调用的关系。Call Chart 曾经比原数据可读性高很多,但它依然不不便发现那些运行工夫很长的代码,这时咱们便须要应用 Flame Chart。
Flame Chart
Flame Chart 提供了一个调用栈的聚合信息。与 Call Chart 不同的是,它的横轴显示的是百分比数值。因为疏忽了工夫线信息,Flame Chart 能够展现每次调用耗费工夫占用整个记录时长的百分比。同时纵轴也被对调了,在顶部展现的是被调用者,底部展现的是调用者。此时的图表看起来越往上越窄,就如同火焰一样,因而得名:
Flame Chart 是基于 Call Chart 来从新组织信息的。从 Call Chat 开始,合并雷同的调用栈,以耗时由长至短对调用栈进行排序,就取得了 Flame Chart:
比照两种图表不难看出,右边的 Call Chart 有具体的工夫信息,能够展现每次调用是何时产生的;左边的 Flame Chart 所展现的聚合信息,则有助于发现一个总耗时很长的调用门路:
Top Down Tree
后面介绍的两种图表,能够帮忙咱们从两种角度纵览全局。而如果咱们须要更准确的工夫信息,就须要应用 Top Down Tree。在 CPU Profiler 中,Top Down 选项卡展现的是一个数据表格,为了便于了解其中各组数据的意义,接下来咱们会尝试构建一个 Top Down Tree。
构建一个 Top Down Tree 并不简单。以 Flame Chart 为根底,您只须要从调用者开始,继续增加被调用者作为子节点,直到整个 Flame Chart 被遍历一遍,您就取得了一个 Top Down Tree:
对于每个节点,咱们关注三个工夫信息:
- Self Time —— 运行本人的代码所耗费的工夫;
- Children Time —— 调用其余办法的工夫;
- Total Time —— 后面两者工夫之和。
有了 Top Down Tree,咱们能轻易将这三组信息归纳到一个表格之中:
上面咱们来看一看这些工夫信息是怎么计算的。左手边是和后面一样的 Flame Chart 示例。左边则是一个 Top Down Tree。
咱们从 A 节点开始:
- A 耗费了 1 秒钟来运行本人的代码,所以 Self Time 是 1;
- 而后它耗费了 9 秒中去调用其余办法,这意味着它的 Children Time 是 9;
- 这样就一共耗费了 10 秒钟,Total Time 是 10;
- B 和 D 以此类推 …
值得注意的是,D 节点只是调用了 C,本人没做任何事,这种状况在办法封装时很常见。所以 D 的 Children Time 和 Total Time 都是 2。
上面是表格齐全开展的状态。当您在 Android Studio 中剖析利用时,CPU Profiler 会实现下面所有的计算,您只有了解这些数字是怎么产生的即可:
比照左右两边: Flame Chart 比拟便于发现总耗时很长的调用链,而 Top Down Tree 则不便察看其中每一步所耗费的准确工夫。作为一个表格,Top Down Tree 也反对按独自维度进行排序,这点同样十分实用。
Bottom Up Tree
当您心愿不便地找到某个办法的调用栈时,Bottom Up Tree 就派上用场了。” 树 ” 如其名,Bottom Up Tree 从底部开始构建,这样咱们就能通过在节点上一直增加调用者来反向构建出树。因为每个独立节点都能够构建出一棵树,所以这里其实是森林 (Forest):
让咱们再做些计算来搞定这些工夫信息。
表格有四行,因为咱们有四个树在森林中。从节点 C 开始:
- Self Time 是 4 + 2 = 6 秒钟;
- C 没有调用其余办法,所以 Children Time 是 0;
- 后面两者相加,总工夫为 6 秒钟。
看起来与 Top Bottom Tree 别无二致。接下来开展 C 节点,计算 C 的调用者 B 和 D 的状况。
在计算 B 和 D 节点的相干工夫时,状况与后面的 Top Bottom Tree 有所不同:
- 因为咱们在构建基于 C 节点的 Bottom Up Tree,所以所有工夫信息也都是基于 C 节点的。这时咱们在计算 B 的 Self Time 时,该当计算 C 被 B 调用的工夫,而不是 B 本身执行的工夫,这里是 4 秒;对于 D 来说,则是 2 秒。
- 因为只有 B 和 D 调用 C 的办法,它们的 Total Time 之和应与 C 的 Total Time 相等。
下一个树是 B 节点的 Bottom Up Tree,它的 Self Time 是 3 秒,Children Time 是用来调用其余办法的工夫,这里只有 C,所以是 2 秒。Total Time 永远都是前两者之和。上面便是整个表格开展的样子:
当您想要察看某个办法如何被调用,比方这个 nanoTime() 办法时,您能够应用 Bottom Up Tree 并察看 nanoTime 办法的子节点列表,通过左边的工夫数据,您能够找到那个您所感兴趣的调用:
备忘表
后面介绍了四种不同的数据图表,并且还具体解释了一些数据是如何被计算出来的。如果您感觉脉络太多很难记住,没关系,上面这个扼要的备忘表就是为您筹备的:
总结
本文介绍了 Android Studio Profiler 中的两种数据分析工具。
其中 Memory Profiler 能够自动检测 Activity 和 Fragment 的内存透露,而通过理解和应用 Memory Profiler 中数据分析性能提供的数据,也能够发现和解决其余类型的内存透露问题。
无关 CPU Profiler 则介绍了 Call Chart、Flame Chart、Top Down、Bottom Up 这四种维度的数据出现。
心愿这些内容可能帮忙您更加理解 Android Profiler。如仍有疑难,欢送在下方留言。也欢送通过 Android Studio 反馈应用中遇到的问题。
您也能够通过视频回顾 2019 Android 开发者峰会演讲 —— 读懂 Android Studio 剖析工具数据。