捕获堆转储以查看在捕获时应用中哪些对象正在使用内存,并识别内存泄漏或导致应用卡顿、冻结甚至崩溃的内存分配行为。在长时间的用户会话后,获取堆转储尤其有用,因为它可能会显示不应再位于内存中的对象。
本页介绍了 Android Studio 提供的用于收集和分析堆转储的工具。或者,您可以从命令行使用 dumpsys
检查您的应用内存,还可以在 Logcat 中查看垃圾回收 (GC) 事件。
为什么应分析您的应用内存
Android 提供了托管内存环境 - 当 Android 确定您的应用不再使用某些对象时,垃圾回收器会将未使用的内存释放回堆中。虽然 Android 查找未使用内存的方式在不断改进,但对于所有 Android 版本,系统都必须在某个时间点短暂地暂停您的代码。大多数情况下,这些暂停难以察觉。不过,如果您的应用分配内存的速度比系统回收内存的速度快,则当回收器释放足够的内存以满足您的分配需要时,您的应用可能会延迟。此延迟可能会导致您的应用跳帧,并使系统明显变慢。
即使您的应用未表现出变慢,但如果存在内存泄漏,应用在转到后台运行时,仍可能保留相应内存。此行为会导致系统强制执行不必要的垃圾回收事件,因而拖慢系统其余部分的内存性能。最终,系统将被迫终止您的应用进程以回收内存。然后,当用户返回您的应用时,应用进程必须完全重启。
如需了解可减少应用内存使用量的编程做法,请阅读管理应用内存。
堆转储概览
如需捕获堆转储,请选择分析内存用量(堆转储)任务(使用 Profiler: run 'app' as debuggable (complete data))捕获堆转储。在转储堆期间,Java 内存量可能会暂时增加。这很正常,因为堆转储与您的应用发生在同一进程中,并需要一些内存来收集数据。捕获堆转储后,您会看到以下内容:
类列表会显示以下信息:
- Allocations:堆中的分配数。
Native Size:此对象类型使用的原生内存总量(以字节为单位)。您会在此处看到采用 Java 分配的某些对象的内存,因为 Android 对某些框架类(如
Bitmap
)使用原生内存。Shallow Size:此对象类型使用的 Java 内存总量(以字节为单位)。
Retained Size:为此类的所有实例而保留的内存总大小(以字节为单位)。
使用堆菜单过滤出特定堆:
- 应用堆(默认):您的应用在其中分配内存的主堆。
- image heap:系统启动映像,包含启动期间预加载的类。此处的分配绝不会移动或消失。
- Zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的。
使用“排列方式”下拉菜单选择分配方式:
- 按类排列(默认):根据类名称对所有分配进行分组。
- Arrange by package:根据软件包名称对所有分配进行分组。
使用课程下拉菜单过滤出课程组:
- 所有类(默认):显示所有类,包括库和依赖项中的类。
- 显示 activity/fragment 泄漏:显示导致内存泄漏的类。
- 显示项目类:仅显示您的项目定义的类。
点击一个类名称以打开实例窗格。列出的每个实例都包含以下信息:
- Depth:从任意 GC 根到选定实例的最短跳数。
- Native Size:原生内存中此实例的大小。只有在使用 Android 7.0 及更高版本时,才会看到此列。
- Shallow Size:Java 内存中此实例的大小。
- Retained Size:此实例所支配内存的大小(根据支配项树)。
点击某个实例可显示实例详情,包括其字段和引用。在 Java 中,常见的字段和引用类型是结构化类型 、数组 和基元数据类型 。右键点击某个字段或引用可前往源代码中的关联实例或代码行。
- 字段:显示此实例中的所有字段。
- 引用:显示对 Instance 标签页中突出显示的对象的每个引用。
查找内存泄漏
如需快速过滤出可能与内存泄漏相关的类,请打开类下拉菜单,然后选择显示 activity/fragment 泄漏。Android Studio 会显示它认为表明应用中的 Activity
和 Fragment
实例存在内存泄漏的类。过滤器显示的数据类型包括:
- 已销毁但仍被引用的
Activity
实例。 - 没有有效的
FragmentManager
但仍被引用的Fragment
实例。
请注意,在以下情况下,过滤器可能会产生误报:
- 已创建
Fragment
,但尚未使用它。 - 正在缓存
Fragment
,但它不是FragmentTransaction
的一部分。
如需更手动地查找内存泄漏,请浏览类和实例列表,查找保留大小较大的对象。请注意由下列任意情况引起的内存泄漏:
- 长时间引用
Activity
、Context
、View
、Drawable
和其他对象,可能会保持对Activity
或Context
容器的引用。 - 可以保持
Activity
实例的非静态内部类,如Runnable
。 - 对象保持时间比所需时间长的缓存。
发现潜在的内存泄漏时,请使用实例详情中的字段和引用标签页跳转到感兴趣的实例或源代码行。
触发内存泄漏以进行测试
如需分析内存用量,您应对应用代码施加压力并尝试强制内存泄漏。在应用中引发内存泄漏的一种方式是,先让其运行一段时间,然后再检查堆。泄漏在堆中可能逐渐汇聚到分配顶部。不过,泄漏越小,您越需要运行更长时间的应用才能看到泄漏。
您还可以通过以下某种方式触发内存泄漏:
- 在不同的 activity 状态下,先将设备从竖屏旋转为横屏,再将其旋转回来,这样反复旋转多次。旋转设备经常会使应用泄露
Activity
、Context
或View
对象,因为系统会重新创建Activity
,而如果您的应用在其他地方保持对这些对象其中一个的引用,系统将无法对其进行垃圾回收。 - 在不同的 activity 状态下,在您的应用与其他应用之间切换。例如,前往主屏幕,然后返回您的应用。
导出和导入堆转储记录
您可以从性能分析器的过往录制内容标签页导出和导入堆转储文件。Android Studio 会将录制内容另存为 .hprof
文件。
或者,如需使用其他 .hprof
文件分析器(如 jhat),您需要将 .hprof
文件从 Android 格式转换为 Java SE .hprof
文件格式。如需转换文件格式,请使用 {android_sdk}/platform-tools/
目录中提供的 hprof-conv
工具。运行包含两个参数(即原始 .hprof
文件名和转换后 .hprof
文件的写入位置,包括新的 .hprof
文件名)的 hprof-conv
命令。例如:
hprof-conv heap-original.hprof heap-converted.hprof