捕获堆转储

捕获堆转储,以查看在捕获时刻您的应用中哪些对象占用了内存,并识别内存泄漏,或导致卡顿、冻结甚至应用崩溃的内存分配行为。在较长的用户会话后进行堆转储尤其有用,因为它可以显示本应已释放但仍残留在内存中的对象。

本页面介绍了 Android Studio 提供的用于收集和分析堆转储的工具。此外,您还可以通过命令行使用 dumpsys 来检查应用内存,或者在 Logcat 中查看垃圾回收 (GC) 事件

为什么要对应用内存进行性能分析

Android 提供了一个托管内存环境——当 Android 确定您的应用不再使用某些对象时,垃圾回收器会将未使用的内存释放回堆中。Android 寻找未使用内存的方式在不断改进,但在所有 Android 版本中,系统有时必须短暂地暂停您的代码。大多数情况下,这种暂停是无法察觉的。但是,如果您的应用分配内存的速度快于系统回收内存的速度,您的应用可能会在收集器释放足够内存以满足分配需求时产生延迟。这种延迟可能会导致应用丢帧,并产生明显的卡顿感。

即使您的应用没有表现出明显的卡顿,如果存在内存泄漏,它甚至会在后台运行时保留这些内存。这种行为会通过强制执行不必要的垃圾回收事件,降低系统其余部分的内存性能。最终,系统将被迫终止您的应用进程以回收内存。当用户返回您的应用时,应用进程必须完全重启。

有关可以减少应用内存使用的编程实践的信息,请阅读管理您的应用内存

堆转储概述

要捕获堆转储,请选择“分析内存使用情况 (堆转储)”(Analyze Memory Usage (Heap Dump)) 任务(使用 Profiler: run 'app' as debuggable (complete data))来捕获堆转储。在转储堆的过程中,Java 内存量可能会暂时增加。这是正常的,因为堆转储发生在与您的应用相同的进程中,并且需要一些内存来收集数据。捕获堆转储后,您将看到以下内容:

Android Studio Profiler 中的堆转储视图。

类列表显示以下信息:

  • 分配 (Allocations):堆中的分配数量。
  • 原生大小 (Native Size):此对象类型使用的原生内存总量(以字节为单位)。对于某些在 Java 中分配的对象,您会在此处看到内存占用,因为 Android 将原生内存用于某些框架类,例如 Bitmap

  • 浅层大小 (Shallow Size):此对象类型使用的 Java 内存总量(以字节为单位)。

  • 保留大小 (Retained Size):由于此类所有实例而保留的内存总量(以字节为单位)。

使用堆菜单过滤特定堆:

  • 应用堆 (App heap, 默认):您的应用分配内存的主要堆。
  • 映像堆 (Image heap):系统启动映像,包含在启动期间预加载的类。此处的分配永远不会移动或消失。
  • Zygote 堆 (Zygote heap):Android 系统中应用进程从中派生(fork)的写时复制(copy-on-write)堆。

使用排列下拉菜单选择分配的排列方式:

  • 按类排列 (Arrange by class, 默认):根据类名对所有分配进行分组。
  • 按包排列 (Arrange by package):根据包名对所有分配进行分组。

使用类下拉菜单过滤类组:

  • 所有类 (All classes, 默认):显示所有类,包括来自库和依赖项的类。
  • 显示 Activity/Fragment 泄漏 (Show activity/fragment leaks):显示导致内存泄漏的类。
  • 显示项目类 (Show project classes):仅显示由您的项目定义的类。

点击类名以打开实例 (Instance) 窗格。列出的每个实例包含以下内容:

  • 深度 (Depth):从任何 GC 根到所选实例的最短跳数。
  • 原生大小 (Native Size):此实例在原生内存中的大小。此列仅在 Android 7.0 及更高版本中可见。
  • 浅层大小 (Shallow Size):此实例在 Java 内存中的大小。
  • 保留大小 (Retained Size):此实例所支配的内存大小(根据支配树)。

点击一个实例以显示实例详细信息 (Instance Details),包括其字段 (Fields)引用 (References)。常见的字段和引用类型包括结构化类型 、数组 以及 Java 中的基本数据类型 。右键点击字段或引用可跳转到关联的实例或源代码行。

  • 字段 (Fields):显示此实例中的所有字段。
  • 引用 (References):显示对实例选项卡中突出显示的对象的所有引用。
堆转储工具窗口中的实例字段引用视图。

查找内存泄漏

要快速过滤可能与内存泄漏关联的类,请打开类下拉菜单并选择显示 Activity/Fragment 泄漏。Android Studio 会显示它认为在您的应用中指示 ActivityFragment 实例发生内存泄漏的类。

若要更手动地查找内存泄漏,请浏览类和实例列表,查找保留大小较大的对象。寻找由以下原因引起的内存泄漏:

  • ActivityContext 的长期引用,这可能会导致宿主 Compose 合成图表(例如 ComposeView 及其子可组合项)泄漏。
  • 泄漏 Jetpack Compose State 对象 (MutableState)、状态持有者或捕获了 Context 的 lambda。
  • DisposableEffectonDispose 块中忘记清理侦听器或观察者。
  • 非静态内部类,例如可以持有 Activity 实例的 Runnable
  • 缓存持有对象的时间超过了必要时间。

当您发现潜在的内存泄漏时,请使用实例详细信息中的字段引用选项卡跳转到感兴趣的实例或源代码行。

触发内存泄漏以进行测试

为了分析内存使用情况,您应该对应用代码施加压力并尝试强制产生内存泄漏。诱发应用内存泄漏的一种方法是在检查堆之前让它运行一段时间。泄漏可能会逐渐积累到堆中分配列表的前列。不过,泄漏越小,您需要运行应用的时间就越长才能观察到它。

您还可以通过以下方式之一触发内存泄漏:

  • 在不同的 Activity 状态下多次将设备从纵向旋转为横向,然后再旋转回来。如果您的应用在异步操作或状态持有者内部持有了对 ActivityContext 的引用,旋转设备通常会导致应用泄漏 Activity(从而导致其宿主 Compose UI 树和关联的状态树泄漏)。
  • 在不同的 Activity 状态下,在您的应用和其他应用之间切换。例如,导航到主屏幕,然后返回您的应用。

导出和导入堆转储记录

您可以从 Profiler 中的过去记录 (Past Recordings) 选项卡导出和导入堆转储文件。Android Studio 会将记录保存为 .hprof 文件。

或者,如果要使用其他 .hprof 文件分析器(如 jhat),则需要将 .hprof 文件从 Android 格式转换为 Java SE .hprof 文件格式。要转换文件格式,请使用 {android_sdk}/platform-tools/ 目录中提供的 hprof-conv 工具。使用两个参数运行 hprof-conv 命令:原始 .hprof 文件名和写入转换后 .hprof 文件的位置(包括新的 .hprof 文件名)。例如:

hprof-conv heap-original.hprof heap-converted.hprof

其他资源