📜  如何使用 Android Studio 内存分析器?

📅  最后修改于: 2022-05-13 01:58:44.628000             🧑  作者: Mango

如何使用 Android Studio 内存分析器?

Android Profiler 取代了 Android Studio 3.0 及更高版本中的 Android Monitor 功能。 Android Profiler 工具为您提供有关您的应用如何消耗 CPU、内存、网络和电池资源的实时信息。从菜单栏中选择视图>工具窗口>探查器

图 0. 导航到 Android Profiler

单击工具栏中的Profile以打开 Profiler 窗口。如果“选择部署目标”窗口提示,请选择在其上配置您的程序的设备。如果通过 USB 连接设备后未显示设备,请确保 USB 调试已打开。 Android Profiler 会列出所有正在运行的进程,即使如果您使用的是 Android Emulator 或 root 设备,它们也无法调试。当您开始可调试程序时,默认情况下会选择该进程。在您拔下设备或选择结束会话之前,Android Profiler 会继续收集分析数据。

图 1. 使用 Memory Profiler 进行 CPU 分析

  1. Android Profiler 显示当前分析的进程和设备。
  2. 选择要在“会话”窗口中查看的会话,或启动新的分析会话。
  3. 单击缩放按钮以选择要查看多少时间线,或使用附加到实时按钮直接获取实时更新。
  4. 用户输入时间线显示键盘活动、音量控制调整和屏幕旋转等事件。
  5. 共享时间线视图,显示 CPU、内存、网络和能源使用的图表。

安卓内存管理

堆中的每个内存分配都由 Android 虚拟机跟踪。堆是系统分配Java/Kotlin 对象的内存部分。垃圾收集是恢复未使用内存的过程。其目标如下:

  1. 寻找没有人需要的物品。
  2. 通过回收将这些对象使用的内存返回到堆中。

垃圾收集不是您通常要求的。相反,系统使用一组标准来确定何时应该执行。 Android 对每个程序的堆大小进行了限制,以允许多任务处理。此文件的大小将根据设备上可用的 RAM 量而有所不同。当堆容量已满并且系统尝试分配额外内存时,您可能会收到 OutOfMemoryError 异常。

垃圾收集的根源

假设您的记忆头中有以下项目:

记忆的头

图 2. 内存头

GC Roots是屏幕顶部的白色项目。它们不被堆中的任何其他项引用。为方便起见,请将 obj9 视为保留以下对象的活动: obj10、obj11 和 obj12。假设您正在从事该活动并推动返回以完成它。系统会将obj7更改为obj9引用。当系统触发垃圾收集器时,它将从 GC 根开始。它将识别出 obj9 和它已存储的其他对象 obj10、obj11 和 obj12 不可访问,并将收集它们。

分析应用程序内存有什么好处?

为了让垃圾收集器完成它的任务,系统必须暂停你的应用程序代码。这个过程通常是检测不到的。但是,您可能会发现您的应用程序很慢并且在其他时间会跳帧。当您的软件分配内存的速度快于系统收集它的速度时,您会看到此错误。已泄漏的内存无法返回到堆中。这会导致发生不必要的垃圾收集事件,从而降低系统速度。为了回收内存,系统最终可能会杀死您的应用程序进程。您应该分析应用程序的 RAM 和其他内存使用情况,以防止出现这些问题。

说到记忆,有几种不同的方法来衡量它。

根据 Android 系统,您在 Memory Profiler 顶部看到的数字基于您的程序已提交的所有私有内存页面。此数字不包括与操作系统或其他程序共享的页面。

内存计数分为以下几类:

  • Java :来自Java或 Kotlin 代码分配对象的内存。
  • 由 C 或 C++ 代码分配的内存称为本机内存。即使您的应用程序不使用 C++,这里也可能需要本机内存,因为 Android 框架使用本机内存来代表您执行不同的任务,例如处理图片资源和其他图形——即使您的代码是用Java或 Kotlin 编写的.
  • 图形内存用于在屏幕上显示像素的图形缓冲区队列,例如 GL 表面和 GL 纹理。 (请注意,这是与 CPU 共享的内存,而不是专用的 GPU 内存。)
  • Stack :您的应用程序的本机和Java堆栈都消耗此内存。这通常与您的软件使用的线程数有关。
  • Code :分配给应用程序代码和资源的内存,例如 dex 字节码、优化或编译的 dex 代码、.so 库和字体。
  • 其他:您的软件使用的内存,系统不确定如何分类

您的程序分配了一定数量的Java/Kotlin 对象。不包括在 C 和 C++ 中分配的对象。此分配计数仅在 Memory Profiler 连接到您正在运行的应用程序时开始计算,该应用程序连接到运行 Android 7.1 或更低版本的设备。结果,在分析开始之前分配的所有对象都下落不明。但是,在 Android 8.0 及更高版本上,设备上的分析工具会跟踪所有分配,因此该数字始终代表您的应用程序中未完成的Java对象的总数。

与之前的 Android Monitor 工具的内存计数相比,新的 Memory Profiler 以不同的方式捕获您的内存,因此您的内存使用量似乎有所增加。 Memory Profiler 会监控更多类别,这些类别会增加总数,但如果您只关心Java堆内存,“Java”数字应该与先前工具的结果相同。新数字说明了自 Zygote 派生以来已分配给应用Java堆的所有物理内存页面,即使它与您在 Android Monitor 中观察到的不完全匹配。因此,您将能够准确地看到您的程序正在使用多少物理内存。

查看内存分配

内存分配解释了每个Java对象和 JNI 引用是如何在您的内存中分配的。 Memory Profiler 可能会向您显示以下有关对象分配的信息:

送出了什么样的东西,占用了多少空间?

每个分配的堆栈跟踪,包括它发生的线程。

重新分配项目时(仅在使用 Android 8.0 或更高版本的设备时)。

如果您的设备运行的是 Android 8.0 或更高版本,您可以随时检查您的对象分配:通过拖动时间线选择您希望查看分配的区域(如视频 1 所示)。无需开始录制会话,因为 Android 8.0 及更高版本带有一个设备上的分析工具,可以实时监控您的应用程序的分配。

按照这些程序检查分配记录

查看列表中可能泄露的堆计数极高的项目。为了更容易发现已知类,请通过单击“类名”列标题按字母顺序排序。然后从下拉菜单中选择一个类。如图 3 所示,Instance View 窗格显示在右侧,显示该类的每个实例。

或者,您可以通过按 Control+F(在 Mac 上为 Command+F)并在搜索区域中键入类或包名称,或选择过滤器来快速定位项目。如果您从下拉菜单中选择按调用堆栈排列,您也可以按方法名称搜索。如果您希望使用正则表达式,请选中 Regex 旁边的框。如果您的搜索词组区分大小写,请选中匹配大小写旁边的框。在“实例视图”窗口中选择一个实例。调用堆栈选项卡显示在下方,指示该实例被分配的位置和线程。右键单击 Call Stack 选项卡中的任意行并选择 Jump to Source 以在编辑器中打开该代码。

图 3. 检查分配记录

在分析应用时提高应用的性能

默认情况下,内存分析器会频繁对内存分配进行采样,以在分析期间优化应用程序性能。在运行 API 级别 26 或更高级别的设备上进行测试时,分配跟踪选项可用于调整此行为。以下是可用的替代方案:

  • Full :捕获所有对象的所有内存分配。在 Android Studio 3.2 及之前的版本中,这是默认行为。如果您的软件分配了大量对象,您可能会在分析它时看到明显的减速。
  • 对象:内存中的分配是定期采样的。这是默认设置,它在分析时对应用程序性能的影响很小。在短时间内分配大量对象的应用程序可能仍会遇到明显的延迟。
  • Off :禁用应用程序的内存跟踪。

捕捉垃圾

堆转储显示在进行堆转储时程序中的哪些对象正在消耗内存。堆转储可以通过揭示您怀疑不应该存在的内存中挥之不去的东西来帮助发现内存泄漏,尤其是在长时间的用户会话之后。捕获堆转储后,您可以看到以下内容:

  1. 已为您的应用分配了哪些类型的对象,以及每个已分配了多少。
  2. 每个项目使用多少内存?
  3. 您在哪里保存对代码中每个对象的引用?
  4. 调用堆栈上分配对象的位置。 (目前只有当您在使用 Android 7.1 及更低版本记录分配时捕获堆转储时,调用堆栈才可用于堆转储。)

在 Memory Profiler 工具栏中,单击 Dump Java heap 以捕获堆转储。清空堆时使用的Java内存量可能会短暂上升。这是典型的,因为堆转储发生在与您的程序相同的进程中,并从内存中收集数据。

图 4. 吸附堆

要检查您的堆,请执行以下步骤

查看列表以查看是否有任何可能已泄漏的堆计数异常高的对象。单击类名称列标题按字母顺序排序,这将有助于发现已识别的类。之后,从下拉菜单中选择一个类。如图 6 所示,Instance View 窗格显示在右侧,显示该类的每个实例。

从“实例视图”窗口中选择一个实例。参考选项卡出现在下面,它列出了链接到该项目的所有对象。

如果您在 References 选项卡中看到似乎正在泄漏内存的引用,请右键单击它并选择 Go to Instance。这会从堆转储中选择适当的实例并显示它自己的实例数据。在堆转储中查找由以下任何一项引起的内存泄漏:

  • Activity、View、Context、Drawable 和其他可能有引用的对象。
  • 可以存储 Activity 实例但不是静态的内部类,例如 Runnable。
  • 将事物保存在内存中的时间超过应有时间的缓存。