记录 Java/Kotlin 分配

记录 Java/Kotlin 分配有助于您识别可能会导致性能问题的不理想内存模式。该分析器可以显示以下有关对象分配的信息:

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

您应在正常和极端用户互动期间记录内存分配,以准确找出代码在短时间内分配过多对象或分配的对象发生泄漏的位置。详细了解为什么应分析您的应用内存。

如何记录 Java/Kotlin 分配情况

如需记录 Java/Kotlin 分配情况,请从性能分析器的 Home 标签页中选择 Track Memory Consumption (Java/Kotlin Allocations) 任务。请注意,您需要一个可调试的应用(使用 Profiler: run 'app' as debuggable (complete data))来记录 Java/Kotlin 分配。

Android Studio 性能分析器中的分配跟踪记录控件
Android Studio 性能分析器中的分配跟踪记录控件。

默认情况下,Android Studio 会捕获内存中的所有对象分配。如果您的应用分配了大量对象,您可能会在进行性能剖析时发现应用明显变慢。如需在进行性能分析时提高性能,请前往分配跟踪下拉菜单,然后选择采样,而不是完整。 采样时,性能分析器会定期收集内存中的对象分配情况。

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

Java/Kotlin 分配概览

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

已解析的 Java 和 Kotlin 分配记录概览
已解析的 Java 和 Kotlin 分配记录概览。
  • 事件时间轴会显示 activity 状态、用户输入事件和屏幕旋转事件。
  • 内存用量时间轴会显示以下信息。选择时间轴的一部分,可过滤到特定时间范围。
    • 一个堆积图,显示每个内存类别使用的内存量,如左侧的 y 轴和顶部的颜色键所示。
    • 虚线表示已分配对象的数量,如右侧 y 轴所示。
    • 每个垃圾回收事件的图标。
  • 表格标签页会显示类别列表。总计数是所选时间范围结束时的分配数量(分配减去取消分配),因此首先调试总计数值最高的类可能比较合理。如果您更想根据所选时间范围内的峰值分配量来排查类问题,请按分配量确定优先级。同样,Remaining Size 是 Allocations Size 减去 Deallocations Size(以字节为单位)。
  • 当您点击表格列表中的某个类时,实例窗格会打开,其中包含关联对象的列表,包括这些对象的分配时间、解除分配时间及其浅层大小。
  • 可视化标签页会显示所选时间范围内调用堆栈中所有对象的汇总视图。它基本上会显示所显示的实例的调用堆栈占用的总内存量。第一行显示线程名称。默认情况下,对象会根据分配大小从左到右堆叠;您可以使用下拉菜单更改排序。

    显示汇总分配调用堆栈的“可视化”标签页

  • 使用堆下拉菜单可过滤出特定堆。除了捕获堆转储时可用的过滤条件之外,您还可以过滤 JNI 堆中的类,该堆显示了 Java 原生接口 (JNI) 引用的分配和释放位置。

  • 使用排列下拉菜单选择如何排列分配。除了捕获堆转储时可用的排列方式之外,您还可以按调用堆栈进行排列。

如何计算内存

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

内存分配类别的颜色图例
内存分配类别的颜色图例。
  • Java:从 Java 或 Kotlin 代码分配的对象的内存。
  • Native:从 C 或 C++ 代码分配的对象的内存。

    即使您的应用中不使用 C++,您也可能会看到此处使用了一些原生内存,因为即使您编写的代码采用 Java 或 Kotlin 语言,Android 框架仍使用原生内存代表您处理各种任务,如处理图片素材资源和其他图形。

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

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

  • 代码:应用用于代码和资源(例如 DEX 字节码、经过优化或编译的 DEX 代码、so 库和字体。

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

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

检查分配记录

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

  1. 浏览表格标签页中的类列表,查找分配或总计数值异常大(取决于您要优化的目标)且可能存在泄漏的对象。
  2. 在 Instance View 窗格中,点击一个实例。系统会打开 Fields 或 Allocation Call Stack 标签页,具体取决于哪个标签页适用于该实例。使用字段或分配调用堆栈标签页中的信息,确定实例是真正需要还是不必要的重复。
显示“字段”和“分配调用堆栈”标签页的实例详细信息窗格
显示“字段”和“调用堆栈”标签页的“实例详细信息”窗格。

右键点击任意列表条目,即可跳转到相关源代码。

查看全局 JNI 引用

Java 原生接口 (JNI) 是一个允许 Java 代码和原生代码相互调用的框架。JNI 引用由原生代码进行管理,因此可能会出现以下问题:

  • 原生代码使用的 Java 对象保持活动状态过长时间。
  • 如果 JNI 引用在未先明确删除的情况下被舍弃,Java 堆上的某些对象可能会变得无法访问。
  • 全局 JNI 引用限制已用尽。

如需排查此类问题,请在性能分析器中选择 View JNI heap,以浏览所有全局 JNI 引用,并按 Java 类型和原生调用堆栈对其进行过滤。右键点击 Fields 标签页中的实例字段,然后选择 Go to instance,以查看相关的分配调用堆栈。

显示全局 JNI 引用的 JNI 堆视图
显示全局 JNI 引用的 JNI 堆视图。

Allocation Call Stack 标签页会显示在代码中将 JNI 引用分配和释放到了什么位置。

“Allocation Call Stack”标签页,其中显示了 JNI 引用已分配并发布到了哪些位置
“Allocation Call Stack”标签页,显示 JNI 引用已分配并发布到了哪些位置。

如需详细了解 JNI,请参阅 JNI 提示。