录制 Java/Kotlin 内存分配

录制 Java/Kotlin 内存分配可帮助您识别可能导致性能问题的 undesirable 内存模式。性能分析器可以显示有关对象分配的以下信息:

  • 分配了哪些类型的对象以及它们使用了多少空间。
  • 每次分配的堆栈跟踪,包括在哪个线程中。
  • 对象何时被解除分配。

您应该在正常和极端的交互过程中录制内存分配,以准确识别您的代码是在短时间内分配了太多对象,还是分配了导致内存泄漏的对象。了解更多关于为什么要分析您的应用内存的信息

如何录制 Java/Kotlin 内存分配

要录制 Java/Kotlin 内存分配,请从性能分析器主页选项卡中选择跟踪内存消耗(Java/Kotlin 内存分配)任务。请注意,您需要一个可调试的应用(使用 Profiler: run 'app' as debuggable (complete data))才能录制 Java/Kotlin 内存分配。

Android Studio 默认捕获内存中所有对象分配。如果您的应用分配了大量对象,您可能会在分析时观察到应用的明显减速。为了提高分析时的性能,请转到分配跟踪下拉列表,选择采样而非完整。采样时,性能分析器会定期收集内存中的对象分配。

要在录制时强制执行垃圾回收事件,请点击垃圾桶图标

Java/Kotlin 内存分配概览

停止录制后,您会看到以下内容:

  • 事件时间轴显示活动状态、用户输入事件和屏幕旋转事件。
  • 内存使用时间轴显示以下信息。选择时间轴的一部分可按特定时间范围进行过滤。
    • 每个内存类别所使用的内存量堆叠图,如左侧 y 轴和顶部颜色键所示。
    • 虚线表示分配的对象数量,如右侧 y 轴所示。
    • 每个垃圾回收事件的图标。
  • 表格选项卡显示类列表。总计数是选定时间范围结束时的分配数量(分配减去解除分配),因此首先调试总计数值最高的类可能更有意义。如果您更关注根据选定时间范围内的峰值分配来排查类,则按分配进行优先级排序。同样,剩余大小分配大小减去解除分配大小(以字节为单位)。
  • 当您点击表格列表中的一个类时,实例窗格会打开,其中包含相关对象列表,包括它们的分配时间、解除分配时间以及它们的浅层大小
  • 可视化选项卡显示了选定时间范围内调用堆栈中所有对象的聚合视图。它本质上向您展示了带有所示实例的调用堆栈总共占用了多少内存。第一行显示线程名称。默认情况下,对象根据分配大小从左到右堆叠;使用下拉列表更改排序。

  • 使用堆下拉列表可按特定堆进行过滤。除了捕获堆转储时可用的过滤器,您还可以过滤 JNI 堆中的类,该堆显示 Java 本机接口 (JNI) 引用分配和释放的位置。

  • 使用排列下拉列表选择如何排列分配。除了捕获堆转储时可用的排列,您还可以按调用堆栈排列。

内存如何计数

您在顶部看到的数字是根据 Android 系统,您的应用已提交的所有私有内存页面的总和。此计数不包括与系统或其他应用共享的页面。内存计数中的类别如下:

  • Java:来自 Java 或 Kotlin 代码分配的对象的内存。
  • 原生:来自 C 或 C++ 代码分配的对象的内存。

    即使您在应用中不使用 C++,您也可能会看到这里使用了部分原生内存,因为 Android 框架会使用原生内存来为您处理各种任务,例如处理图像资产和其他图形时,即使您编写的代码是 Java 或 Kotlin。

  • 图形:用于图形缓冲区队列以将像素显示到屏幕的内存,包括 GL 表面、GL 纹理等。请注意,这是与 CPU 共享的内存,而不是专用的 GPU 内存。

  • 堆栈:您的应用中原生和 Java 堆栈使用的内存。这通常与您的应用正在运行的线程数量有关。

  • 代码:您的应用用于代码和资源的内存,例如 DEX 字节码、优化或编译的 DEX 代码、.so 库和字体。

  • 其他:您的应用使用的内存,系统不确定如何分类。

  • 已分配:您的应用分配的 Java/Kotlin 对象数量。这不包括在 C 或 C++ 中分配的对象。

检查分配记录

要检查分配记录,请按照以下步骤操作:

  1. 浏览表格选项卡中的类列表,查找分配总计数值异常大(取决于您要优化什么)且可能已泄漏的对象。
  2. 实例视图窗格中,单击一个实例。根据该实例的适用情况,将打开字段分配调用堆栈选项卡。使用字段分配调用堆栈选项卡中的信息,以确定实例是否确实需要或是不必要的重复。

右键单击任何列表条目可跳转到相关的源代码。

查看全局 JNI 引用

Java Native Interface (JNI) 是一个允许 Java 代码和原生代码相互调用的框架。JNI 引用由原生代码手动管理,因此可能发生以下问题:

  • 原生代码使用的 Java 对象存活时间过长。
  • 如果在未显式删除 JNI 引用的情况下丢弃了该引用,Java 堆上的某些对象可能会变得无法访问。
  • 全局 JNI 引用限制已用尽。

要解决此类问题,请在性能分析器中选择查看 JNI 堆以浏览所有全局 JNI 引用,并按 Java 类型和原生调用堆栈进行过滤。右键单击字段选项卡中的实例字段,然后选择转到实例以查看相关的分配调用堆栈。

分配调用堆栈选项卡显示 JNI 引用在您的代码中分配和释放的位置。

有关 JNI 的更多信息,请参阅JNI 技巧