使用Android Memory Profiler

Memory Profiler是Android Profiler中的一个组件,它能够帮助您识别内存泄漏和可能致使卡顿、冻结甚至应用程序崩溃的内存抖动。它显示一个应用程序内存使用的实时图表,并容许你抓取堆栈信息、强行垃圾收集和跟踪内存分配。html

要打开Memory Profiler,请执行如下步骤:android

  1. 单击View > Tool Windows > Profiler(也能够单击工具栏中的Profile图标)。
  2. 从Android Profiler工具栏中选择要分析的设备和应用程序进程。若是已经过USB链接设备,但未看到列出的设备,请确保已启用USB调试。
  3. 单击MEMORY时间轴中的任意位置打开Memory Profiler。

为何要分析应用程序内存

Android提供了一个托管内存环境,当它肯定应用程序再也不使用某些对象时,垃圾收集器会将未使用的内存释放回堆中。Android寻找未使用内存的方式正在不断改进,但在全部Android版本中,系统必须短暂暂停代码。大多数时候,停顿是不被感知的。可是,若是应用程序分配内存的速度快于系统回收内存的速度,则应用程序可能会发生延迟,等待回收器释放足够的内存以知足分配。延迟可能致使应用程序发生跳帧并致使明显变慢。正则表达式

即便你的应用程序并无表现出缓慢,但若是它泄露了内存,即便运行在后台也能够占用内存。此行为会致使垃圾回收事件被强制执行,从而下降系统的其他内存性能。最后,系统可能被迫终止应用程序进程以回收内存。所以当用户返回到此应用程序时,它必须彻底从新启动。api

为了帮助防止这些问题,您应该经过如下操做使用Memory Profiler进行检查:浏览器

  • 在时间轴中查找可能致使性能问题的不良内存分配模式。
  • 抓取Java堆来查看在任何给定时间哪些对象正在耗尽内存。在一段较长的时间内屡次抓取堆信息有助于识别内存泄漏。
  • 记录正常和极端的用户交互过程当中的内存分配,以肯定代码在短期内分配过多对象的位置或对象泄漏的位置。

Memory Profiler概述

当您第一次打开Memory Profiler时,您将看到应用程序的内存使用的详细时间轴,和可使用的内存工具包括强制垃圾回收、抓取堆信息和记录内存分配。缓存

memory-profiler-callouts_2x.png

如上图所示,Memory Profiler的默认视图包括如下内容:session

  1. 强制垃圾回收事件的按钮。
  2. 抓取堆信息的按钮。注意:仅当链接到运行Android7.1(API级别25)或更低版本的设备时,堆信息按钮右侧才会显示一个用于记录内存分配的按钮。
  3. 用于设置Profiler捕获内存分配的频率的下拉菜单。选择适当的选项能够帮助您在分析时提升应用程序性能。
  4. 用于放大/缩小时间轴的按钮。
  5. 一个跳转到实时内存数据的按钮。
  6. 事件时间轴,显示活动状态、用户输入事件和屏幕旋转事件。
  7. 内存使用时间轴,包括如下内容:app

    • 由每个内存类别使用多少内存的堆叠图,如左边的Y轴和顶部的颜色键所指示的。
    • 虚线表示分配的对象的数目,如右边的y轴所示。
    • 每一个垃圾回收事件的图标。

可是,若是您使用的是运行Android 7.1或更低版本的设备,默认状况下并不是全部分析数据均可见。若是您看到一条消息,上面写着“Advanced profiling is unavailable for the selected process”,则须要启用高级分析才能看到如下内容:框架

  • 事件时间轴
  • 分配的对象数
  • 垃圾回收事件

在Android 8.0及更高版本上,高级分析在可调试的应用程序上始终开启。jvm

如何计算内存

你在Memory Profiler顶部看到的数字基于您的应用经过Android 系统提交的全部私有内存页面。此计数不包括与系统或其余应用程序共享的页面。

memory-profiler-counts_2x.png

内存计数中的类别以下:

  • Java:从Java或Kotlin代码中分配对象的内存。
  • Native:从C或C++代码分配对象的内存。即便您在应用程序中不使用C++,也可能会看到一些本地内存,由于Android Framework表明你使用Native内存来处理各类任务,例如处理图像资源和其余图形,即便您编写的代码是Java或KOTLIN。
  • Graphics:用于在屏幕上显示(包括GL曲面、GL纹理等)的图形缓冲队列的内存。(请注意,这是与CPU共享的内存,而不是专用的GPU内存。)
  • Stack:应用程序中Native堆栈和Java堆栈使用的内存。这一般与应用程序正在运行的线程数有关。
  • Code:应用程序用于代码和资源的内存,例如dex字节码、优化或编译的dex代码、.so库和字体。
  • Others:系统不肯定如何分类的应用程序使用的内存。
  • Allocated:应用程序分配的Java/Kotlin对象数,不计算在C或C++中分配的对象。当链接到运行Android7.1及更低版本的设备时,此分配计数仅在Memory Profiler链接到正在运行的应用程序时开始。所以,在开始分析以前分配的任何对象都不会被考虑在内。可是,Android 8.0及更高版本包含一个设备内置分析工具,该工具可跟踪全部分配。所以在android8.0及更高版本上,此数字始终表示应用程序中Java对象总数。

与以前Android Monitor中内存工具的计数相比,新的Memory Profiler以不一样的方式记录您的内存。于是,内存使用率看起来会更高了。Memory Profiler监视一些额外的类别,这些类别增长了总的内存。可是若是您只关心Java堆内存,那么“Java”数值应该与前一个工具中的值相似。可是Java数值可能与您在Android Monitor中看到的不彻底匹配,新数值统计了自从Zygote派生以来应用程序的Java堆分配的全部物理页面。所以,它提供了应用程序实际使用的物理内存量的精确表示。

注意:使用搭载 Android 8.0(API 级别 26)及更高版本的设备时,Memory Profiler 还会显示应用中的一些误报的原生内存使用量,而这些内存其实是分析工具使用的。对于大约 100000 个对象,最多会使报告的内存使用量增长 10MB。在 IDE 的将来版本中,这些数字将从您的数据中过滤掉。

查看内存分配

内存分配向您展现了内存中的每一个Java对象和JNI引用是如何分配。具体来讲,Memory Profiler能够向您显示如下有关对象分配的信息:

  • 分配了哪些类型的对象以及它们使用了多少空间。
  • 每一个分配的堆栈跟踪,包括在哪一个线程中。
  • 对象被释放时间(仅当使用Android 8.0或更高版本的设备时)。

若是您的设备运行的是Android 8.0或更高版本,您能够随时查看对象分配,以下所示:在时间轴中拖动以选择要查看分配的区域。不须要开始录制会话,由于Android8.0及更高版本包含一个设备内置分析工具,能够不断跟踪应用程序的分配。详细内容能够参考视频:高版本查看内存分配

若是您的设备运行的是Android 7.1或更低版本,请单击Memory Profiler工具栏中的Record memory allocations图标。录制时,Memory Profiler会跟踪应用程序中发生的全部分配。完成后,单击Stop recording图标以查看分配。详细内容能够参考视频:低版本查看内存分配

选择时间线的某个区域后(或在使用运行Android7.1或更低版本的设备完成录制会话时),已分配对象的列表将显示在时间线下方,按类名分组并按堆计数排序。

注意:在 Android 7.1 及更低版本上,您最多能够记录 65535 个分配。若是您的记录会话超出此限制,则记录中仅保存最新的 65535 个分配。(在 Android 8.0 及更高版本上,则没有实际的限制。)

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

  1. 浏览列表以查找堆计数异常大且可能泄漏的对象。若要查找已知类,请单击Class Name列标题按字母顺序排序,而后单击类名。Instance View窗格将出如今右侧,显示该类的每一个实例,以下图所示。或者,能够经过单击Filter 图标或按 Ctrl+F 键,并在搜索字段中输入类或包名称来快速定位对象。若是从下拉菜单中选择Arrange by callstack,还能够按方法名进行搜索。若是要使用正则表达式,请选中Regex旁边的复选框。若是搜索查询区分大小写,请选中Match case旁边的复选框。
  2. Instance View窗格中,单击实例。Call Stack标签将出如今下面,显示该实例的分配位置和线程。
  3. Call Stack标签中,右键单击任意行,而后选择Jump to Source,就能够在编辑器中打开该代码。

memory-profiler-allocations-detail_2x.png

您可使用已分配对象列表上方的两个菜单来选择要检查的堆以及如何组织数据。

从左侧的菜单中,选择要检查的堆:

  • default heap:系统未指定堆时。
  • image heap:系统启动镜像,包含在启动期间预加载的类。这里的分配保证不会移动或消失。
  • zygote heap:当应用程序进程从Android系统中派生出来时的写时拷贝堆。
  • app heap:应用程序分配内存的主堆。
  • JNI heap:这个堆显示了Java Native接口(JNI)引用的分配和释放的位置。

从右侧的菜单中,选择如何组织分配:

  • Arrange by class:根据类名对全部分配进行分组。这是默认设置。
  • Arrange by package:根据包名称对全部分配进行分组。
  • Arrange by callstack:将全部分配分组到其相应的调用堆栈中。

在分析时提升应用程序性能

为了提升分析时的应用程序性能,默认状况下,内存探查器按期对内存分配进行采样。在运行API级别26或更高级别的设备上测试时,可使用Allocation Tracking下拉列表更改此行为。可用选项以下:

  • Full:捕获内存中的全部对象分配。这是Android Studio 3.2和更早版本中的默认行为。若是您的应用程序分配了不少对象,那么在分析时,您可能会看到应用程序的速度明显减慢。
  • Sampled:按期采样内存中的对象分配。这是默认选项,在分析时对应用程序性能的影响较小。在短期内分配大量对象的应用程序可能仍然会显示出明显的减速。
  • Off:中止跟踪应用程序的内存分配。
注意:默认状况下,Android Studio在执行CPU录制时中止跟踪实时分配,并在CPU录制完成后将其从新打开。您能够在“CPU记录配置”对话框中更改此行为。

查看全局JNI引用

Java Native Interface(JNI)是一个容许Java代码和Native代码相互调用的框架。JNI引用是由Native代码进行管理的,所以Native代码使用的Java对象可能会存活很长时间。若是在没有显式删除JNI引用的状况下丢弃JNI引用,Java堆上的某些对象可能会变得不可访问。此外,还可能会耗尽全局JNI引用的限制。要解决此类问题,请使用Memory Profiler中的JNI heap来浏览全部全局JNI引用,并按Java类型和Native调用堆栈筛选它们。有了这些信息,您能够找到什么时候何地建立和删除全局JNI引用。

当应用程序运行时,选择要检查的时间轴的一部分,而后从类列表上方的下拉菜单中选择JNI heap。接下来,您就能够像往常同样检查堆中的对象,并双击Allocation Call Stack选项卡中的对象,查看在代码中JNI引用的分配和释放的位置,以下图所示。
memory-profiler-jni-heap_2x.png

要检查应用程序JNI代码的内存分配,必须将应用程序部署到运行Android 8.0或更高版本的设备上。

抓取堆信息

堆信息能显示在抓取堆信息时应用程序中的哪些对象正在使用内存。特别是在长时间的用户会话以后,经过分析堆信息中是否存在您认为不该该存在的对象,能够用来帮助识别内存泄漏。抓取堆信息后,你能够查看如下内容:

  • 你的应用程序分配了哪些类型的对象,以及每种对象的数量。
  • 每一个对象正在使用的内存量。
  • 在代码中保存对每一个对象的引用的位置。
  • 分配对象的调用堆栈。(调用堆栈当前仅在Android 7.1及更低版本中提供,只有在分配期间抓取堆信息才能显示。)

要抓取堆信息,请单击Memory Profiler工具栏中的Dump Java heap图标。在抓取期间,Java内存量可能会临时增长。这是正常的,由于堆抓取发生在与应用程序相同的进程中,须要一些内存来收集数据。堆信息在内存时间轴的下方,显示堆中全部类的类型,以下图所示。

memory-profiler-dump_2x.png

若是须要更精确地了解堆的抓取时间,能够经过调用dumpHprofData()在应用程序代码的关键点抓取堆信息。

在类列表中,能够看到如下信息:

  • Allocations:堆中的分配数。
  • Native Size:此对象类型使用的Native内存总量(字节)。此列仅适用于Android 7.0及更高版本。这里您将看到Java中分配的一些对象的内存,由于Android在一些framework类(如位图)使用Native内存。
  • Shallow Size:此对象类型使用的Java内存总量(字节)。
  • Retained Size:为该类的全部实例而保留的内存的总大小(字节)。

您可使用已分配对象列表上方的两个菜单来选择要检查的堆信息以及如何组织数据。

从左侧的菜单中,选择要检查的堆:

  • default heap:系统未指定堆时。
  • app heap:应用程序分配内存的主堆。
  • image heap:系统启动镜像,包含在启动期间预加载的类。这里的分配保证不会移动或消失。
  • zygote heap:应用程序进程从Android系统中派生时的写时拷贝堆。

从右侧的菜单中,选择如何组织分配:

  • Arrange by class:根据类名对全部分配进行分组。这是默认设置。
  • Arrange by package:根据包名称对全部分配进行分组。
  • Arrange by callstack:将全部分配分组到其相应的调用堆栈中。只有在分配期间抓取堆信息,此选项才起做用。即使如此,堆中也可能有在开始录制以前分配的对象,所以在显示这些分配时,只是按类名列出。

默认状况下,列表按Retained Size列排序。若要按其余列中的值排序,请单击该列的标题。

单击类名打开右侧的IInstance View窗口(以下图所示),每一个列出的实例包括如下内容:

  • Depth:从任意GC根到所选实例的最短跃点数。
  • Native Size:Native内存中此实例的大小。此列仅适用于Android 7.0及更高版本。
  • Shallow Size:这个实例在Java内存中的大小。
  • Retained Size:此实例支配的内存大小(根据支配树)。
注意:默认状况下,堆信息不会向您显示每一个已分配对象的堆栈轨迹。要获取堆栈轨迹,在点击 Dump Java heap 以前,您必须先开始记录内存分配。而后,您能够在 Instance View 中选择一个实例,并查看 References 标签旁边的 Call Stack 标签,以下图所示。不过,在您开始记录分配以前,可能已分配一些对象,所以不会显示这些对象的调用堆栈。在包含调用堆栈的实例在图标上会有一个“堆栈”标志表示。(遗憾的是,因为堆栈轨迹须要您执行分配记录,所以您目前没法在 Android 8.0 上查看堆信息的堆栈轨迹。)

memory-profiler-dump-stacktrace_2x.png

要检查堆信息,请执行如下步骤:

  1. 浏览列表以查找堆计数异常大且可能泄漏的对象。若要查找已知类,请单击Class Name列标题按字母顺序排序。而后单击类名。Instance View窗格会出如今右侧,显示该类的每一个实例,如上图所示。或者,能够经过单击Filter图标或按Control+F,并在搜索字段中输入类或包名称来快速定位对象。若是从下拉菜单中选择Arrange by callstack,还能够按方法名进行搜索。若是要使用正则表达式,请选中Regex旁边的复选框。若是搜索查询区分大小写,请选中Match case旁边的复选框。
  2. Instance View窗格中,单击实例。References标签将出如今下面,显示对该对象的每一个引用。或者,单击实例名称旁边的箭头以查看其全部字段,而后单击字段名称以查看其全部引用。若是要查看某个字段的实例详细信息,请右键单击该字段并选择Go to Instance
  3. References标签中,若是标识了可能泄漏内存的引用,请右键单击该引用并选择Go to Instance。这将从堆信息中选择相应的实例,从而显示其本身的实例数据。

在堆信息中,注意如下状况可能致使的内存泄漏:

  • 对Activity、Context、View、Drawable和其余对象的长时间引用可能包含对Activity或Context的引用。
  • 能够保存Activity的非静态内部类,如Runnable。
  • 保存对象的时间超过所需时长的缓存。

将堆信息另存为HPROF文件

捕获堆信息后,只有在Profiler运行时,数据才能在Memory Profiler中查看。退出剖析会话时,将丢失堆数据。所以,若是您想保存它以便之后查看,请将堆信息导出到HPROF文件。在Android Studio 3.1及更低版本中,Export capture to file按钮位于时间轴下的工具栏左侧;在Android studio 3.2及更高版本中,Sessions窗格中每一个Heap Dump的右侧都有一个Export Heap Dump按钮。在弹出的Export As对话框中,使用.hprof文件扩展名保存文件。

要使用不一样的HPROF分析器(如jhat),须要将HPROF文件从Android格式转换为Java SE HPROF格式。您可使用android_sdk/platform tools/目录中提供的hprof-conv工具来执行此操做。使用两个参数(原始hprof文件的位置和转换后的hprof文件的写入位置)运行hprof-conv命令。例如:

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

导入堆信息文件

要导入HPROF(.hprof)文件,请单击Sessions窗格中的Start a new profiling session图标,选择Load from file,而后从文件浏览器中选择该文件。也能够经过将HPROF文件从文件浏览器拖动到编辑器窗口中来导入该文件。

分析内存的技巧

在使用Memory Profiler时,您应该给应用程序代码增长压力,试图去暴露内存泄漏。引起应用程序内存泄漏的一种方法是在检查堆以前让它运行一段时间,泄漏可能会逐渐聚集到堆中分配的顶部。可是当泄漏越小时,应用程序就须要运行越长时间,再进行泄漏检查。

您还能够经过如下方式之一触发内存泄漏:

  • 在不一样的Activity状态下,将设备从纵向旋转到横向,再旋转到纵向,如此进行屡次旋转。旋转设备一般会致使应用程序泄漏Activity、Context 或 View对象,由于系统会从新建立该Activity,若是应用程序在其余地方保存了一个对象的引用,则系统没法对其进行垃圾回收。
  • 在处于不一样Activity状态时在应用程序之间进行切换(导航到主屏幕,而后返回您的应用程序)。

参考文档:

Android Developers: memory profiler

相关文章
相关标签/搜索