捕获堆转储可查看在捕获时应用中有哪些对象正在占用内存,并识别内存泄漏,即导致卡顿、冻结甚至应用崩溃的内存分配行为。在长时间的用户会话后捕获堆转储尤其有用,因为这可以显示仍留在内存中但本应不存在的对象。
本页介绍 Android Studio 提供的用于收集和分析堆转储的工具。或者,您也可以使用 dumpsys
从命令行检查应用内存,还可以在 Logcat 中查看垃圾回收 (GC) 事件。
为什么要分析应用内存
Android 提供一个托管内存环境 — 当 Android 确定您的应用不再使用某些对象时,垃圾回收器会将未使用的内存释放回堆。Android 查找未使用内存的方式正在不断改进,但在所有 Android 版本上的某个时间点,系统必须短暂暂停您的代码。大多数情况下,暂停是不可察觉的。但是,如果您的应用分配内存的速度快于系统回收内存的速度,您的应用可能会在回收器释放足够的内存以满足您的分配需求时被延迟。这种延迟可能导致您的应用跳帧并导致明显的卡顿。
即使您的应用没有出现卡顿,如果它内存泄漏,即使在后台运行时也可能保留该内存。这种行为可能通过强制进行不必要的垃圾回收事件来降低系统其余部分的内存性能。最终,系统被迫终止您的应用进程以回收内存。然后,当用户返回您的应用时,应用进程必须完全重新启动。
有关可减少应用内存使用的编程实践的信息,请阅读管理应用内存。
堆转储概览
要捕获堆转储,请选择分析内存使用情况(堆转储)任务(使用Profiler:以可调试模式运行“应用”(完整数据))来捕获堆转储。在转储堆时,Java 内存量可能会暂时增加。这是正常的,因为堆转储发生在与您的应用相同的进程中,并且需要一些内存来收集数据。捕获堆转储后,您会看到以下内容
类列表显示以下信息
- 分配:堆中的分配数量。
原生大小:此对象类型使用的原生内存总量(以字节为单位)。您会看到此处有一些在 Java 中分配的对象的内存,因为 Android 对某些框架类(如
Bitmap
)使用原生内存。浅层大小:此对象类型使用的 Java 内存总量(以字节为单位)。
保留大小:由于此类的所有实例而保留的内存总大小(以字节为单位)。
使用堆菜单过滤到特定堆
- 应用堆(默认):您的应用分配内存的主要堆。
- 图像堆:系统启动映像,包含在启动时预加载的类。此处分配的内存永不移动或消失。
- Zygote 堆:在 Android 系统中,应用进程从此处派生的写时复制堆。
使用排列下拉菜单选择如何排列分配
- 按类排列(默认):根据类名对所有分配进行分组。
- 按包排列:根据包名对所有分配进行分组。
使用类下拉菜单过滤到类组
- 所有类(默认):显示所有类,包括来自库和依赖项的类。
- 显示 activity/fragment 泄漏:显示导致内存泄漏的类。
- 显示项目类:仅显示您的项目定义的类。
点击类名以打开实例窗格。列出的每个实例都包含以下内容
- 深度:从任何 GC 根到选定实例的最短跳数。
- 原生大小:此实例在原生内存中的大小。此列仅适用于 Android 7.0 及更高版本。
- 浅层大小:此实例在 Java 内存中的大小。
- 保留大小:此实例支配的内存大小(根据支配树)。
点击一个实例以显示实例详细信息,包括其字段和引用。常见的字段和引用类型是结构化类型、数组
以及 Java 中的原始数据类型
。右键点击字段或引用可跳转到关联的实例或源代码行。
- 字段:显示此实例中的所有字段。
- 引用:显示对实例选项卡中突出显示的对象的所有引用。
查找内存泄漏
要快速过滤到可能与内存泄漏相关的类,请打开类下拉菜单并选择显示 activity/fragment 泄漏。Android Studio 会显示它认为指示应用中 Activity
和 Fragment
实例存在内存泄漏的类。过滤器显示的数据类型包括以下内容
- 已销毁但仍被引用的
Activity
实例。 - 没有有效
FragmentManager
但仍被引用的Fragment
实例。
请注意,在以下情况下,过滤器可能会产生误报
- 一个
Fragment
已创建但尚未被使用。 - 一个
Fragment
正在被缓存,但不是FragmentTransaction
的一部分。
要更手动地查找内存泄漏,请浏览类和实例列表,查找具有较大保留大小的对象。查找由以下任何原因导致的内存泄漏
- 对
Activity
、Context
、View
、Drawable
和其他可能持有Activity
或Context
容器引用的对象的长期引用。 - 非静态内部类(如
Runnable
>)可能持有Activity
实例。 - 缓存保留对象的时间超过必要时间。
当您发现潜在的内存泄漏时,请使用实例详细信息中的字段和引用选项卡跳转到感兴趣的实例或源代码行。
触发内存泄漏进行测试
要分析内存使用情况,您应该对应用代码进行压力测试并尝试强制内存泄漏。一种在应用中引发内存泄漏的方法是在检查堆之前让它运行一段时间。泄漏可能会逐渐浮出堆的顶部。但是,泄漏越小,您需要运行应用的时间就越长才能看到它。
您还可以通过以下方式之一触发内存泄漏
- 在不同的 activity 状态下,多次将设备从竖屏旋转到横屏再转回竖屏。旋转设备通常会导致应用泄漏
Activity
、Context
或View
对象,因为系统会重新创建Activity
,如果您的应用在其他地方持有对其中一个对象的引用,系统将无法对其进行垃圾回收。 - 在不同的 activity 状态下,在您的应用和其他应用之间切换。例如,导航到主屏幕,然后返回到您的应用。
导出和导入堆转储记录
您可以从分析器中的过去的记录选项卡导出和导入堆转储文件。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