记录 Java/Kotlin 内存分配

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

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

您应该在正常和极端用户交互期间记录内存分配,以准确确定代码是在短时间内分配了太多对象,还是分配了会泄漏的对象。 详细了解为什么要分析应用程序内存.

如何记录 Java/Kotlin 内存分配

要记录 Java/Kotlin 内存分配,从分析器**主页**选项卡中选择**跟踪内存消耗(Java/Kotlin 内存分配)**任务。请注意,您需要一个可分析的应用程序(使用**分析器:以可调试模式运行“应用程序”(完整数据)**)才能记录 Java/Kotlin 内存分配。

Android Studio 默认情况下会捕获内存中的所有对象分配。 如果您的应用分配了大量对象,您可能会在分析期间观察到应用的明显速度下降。 为了提高分析时的性能,请转到“**分配跟踪**”下拉菜单,然后选择“**采样**”而不是“**完整**”。 采样时,分析器会定期收集内存中的对象分配。

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

Java/Kotlin 分配概述

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

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

  • 使用堆下拉菜单筛选到特定堆。 除了在 捕获堆转储时可用的筛选器 之外,您还可以筛选到 JNI 堆中的类,该堆显示 Java 本地接口 (JNI) 引用在何处分配和释放。

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

如何计算内存

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

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

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

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

  • **Stack:**您的应用中本机和 Java 堆栈使用的内存。 这通常与您的应用运行的线程数有关。

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

  • **Others:**您的应用使用的系统不确定如何分类的内存。

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

检查分配记录

要检查分配记录,请执行以下步骤

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

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

查看全局 JNI 引用

Java 本地接口 (JNI) 是一种框架,允许 Java 代码和本机代码相互调用。 JNI 引用由本机代码手动管理,因此可能会出现以下问题

  • 本机代码使用的 Java 对象保持活动时间过长。
  • 如果 JNI 引用在没有先被显式删除的情况下被丢弃,则 Java 堆中的某些对象可能变得不可访问。
  • 全局 JNI 引用限制已用尽。

要排查此类问题,请在分析器中选择**查看 JNI 堆**以浏览所有全局 JNI 引用,并按 Java 类型和本机调用堆栈筛选它们。 右键单击**字段**选项卡中的实例字段,然后选择**转到实例**以查看相关的分配调用堆栈。

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

有关 JNI 的更多信息,请参阅 JNI 提示