捕获堆转储

捕获堆转储可以查看应用中哪些对象在捕获时正在占用内存,并识别内存泄漏或导致卡顿、冻结甚至应用崩溃的内存分配行为。在扩展用户会话后捕获堆转储尤其有用,因为它可以显示仍存在于内存中但不再需要的对象。

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

为什么要分析应用内存

Android 提供了 托管内存环境——当 Android 确定您的应用不再使用某些对象时,垃圾回收器会将未使用的内存释放回堆。Android 查找未使用的内存的方式一直在改进,但在所有 Android 版本上,系统在某个时刻都必须短暂暂停您的代码。大多数情况下,暂停是难以察觉的。但是,如果您的应用分配内存的速度快于系统收集内存的速度,则您的应用可能会在回收器释放足够的内存以满足您的分配请求时延迟。延迟可能导致您的应用跳过帧并导致明显的缓慢。

即使您的应用没有表现出缓慢,如果它泄漏内存,它也可能保留该内存,即使它在后台运行也是如此。此行为可能会减慢系统其余部分的内存性能,因为会强制执行不必要的垃圾回收事件。最终,系统将被迫终止您的应用进程以回收内存。然后,当用户返回到您的应用时,应用进程必须完全重新启动。

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

堆转储概述

要捕获堆转储,请 选择“分析内存使用情况(堆转储)”任务(使用“Profiler: 以可调试模式运行“app”(完整数据)”)来捕获堆转储。在转储堆时,Java 内存量可能会暂时增加。这是正常的,因为堆转储发生在与您的应用相同的进程中,并且需要一些内存来收集数据。捕获堆转储后,您将看到以下内容

类列表显示以下信息

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

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

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

使用堆菜单过滤到某些堆

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

使用排列下拉菜单选择如何排列分配

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

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

  • 所有类(默认):显示所有类,包括来自库和依赖项的类。
  • 显示活动/片段泄漏:显示导致内存泄漏的类。
  • 显示项目类:仅显示项目定义的类。

单击类名以打开“实例”窗格。列出的每个实例都包含以下内容

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

单击实例以显示“实例详细信息”,包括其“字段”和“引用”。常见的字段和引用类型是 Java 中的结构化类型 、数组 和原始数据类型 。右键单击字段或引用以转到关联的实例或源代码行。

  • 字段:显示此实例中的所有字段。
  • 引用:显示对“实例”选项卡中突出显示的对象的所有引用。

查找内存泄漏

要快速过滤到可能与内存泄漏相关的类,请打开类下拉菜单并选择“显示活动/片段泄漏”。Android Studio 会显示它认为指示应用中 ActivityFragment 实例的内存泄漏的类。过滤器显示的数据类型包括以下内容

  • Activity 实例已被销毁,但仍在被引用。
  • Fragment 实例没有有效的 FragmentManager,但仍在被引用。

请注意,过滤器可能会在以下情况下产生误报

  • 已创建 Fragment,但尚未使用。
  • 正在缓存 Fragment,但不是作为 FragmentTransaction 的一部分。

要更手动地查找内存泄漏,请浏览类和实例列表以查找具有较大“保留大小”的对象。查找由以下任何一项引起的内存泄漏

  • ActivityContextViewDrawable 和其他可能持有对 ActivityContext 容器的引用的对象的长期引用。
  • 非静态内部类,例如 Runnable,它可以持有 Activity 实例。
  • 缓存对象的时间过长。

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

触发内存泄漏以进行测试

要分析内存使用情况,您应该对应用代码进行压力测试并尝试强制内存泄漏。在应用中引发内存泄漏的一种方法是在检查堆之前让它运行一段时间。泄漏可能会逐渐上升到堆中分配的顶部。但是,泄漏越小,您需要运行应用的时间就越长才能看到它。

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

  • 在不同的活动状态下多次将设备从纵向旋转到横向,然后再旋转回纵向。旋转设备通常会导致应用泄漏 ActivityContextView 对象,因为系统会重新创建 Activity,如果您的应用在其他地方持有对其中一个对象的引用,则系统无法对其进行垃圾回收。
  • 在不同的活动状态下在您的应用和其他应用之间切换。例如,导航到主屏幕,然后返回到您的应用。

导出和导入堆转储记录

您可以从分析器中的“过去的记录”选项卡 导出和导入 堆转储文件。Android Studio 将记录保存为 .hprof 文件。

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

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