Memory Profiler是Android Profiler中的一个组件,它可以帮助您识别内存泄漏和可能导致卡顿、冻结甚至应用程序崩溃的内存抖动。它显示一个应用程序内存使用的实时图表,并允许你抓取堆栈信息、强行垃圾收集和跟踪内存分配。
要打开Memory Profiler,请执行以下步骤:
Android提供了一个托管内存环境,当它确定应用程序不再使用某些对象时,垃圾收集器会将未使用的内存释放回堆中。Android寻找未使用内存的方式正在不断改进,但在所有Android版本中,系统必须短暂暂停代码。大多数时候,停顿是不被感知的。但是,如果应用程序分配内存的速度快于系统回收内存的速度,则应用程序可能会发生延迟,等待回收器释放足够的内存以满足分配。延迟可能导致应用程序发生跳帧并导致明显变慢。
即使你的应用程序并没有表现出缓慢,但如果它泄露了内存,即使运行在后台也可以占用内存。此行为会导致垃圾回收事件被强制执行,从而降低系统的其余内存性能。最后,系统可能被迫终止应用程序进程以回收内存。因此当用户返回到此应用程序时,它必须完全重新启动。
为了帮助防止这些问题,您应该通过以下操作使用Memory Profiler进行检查:
当您第一次打开Memory Profiler时,您将看到应用程序的内存使用的详细时间轴,和可以使用的内存工具包括强制垃圾回收、抓取堆信息和记录内存分配。
如上图所示,Memory Profiler的默认视图包括以下内容:
内存使用时间轴,包括以下内容:
但是,如果您使用的是运行Android 7.1或更低版本的设备,默认情况下并非所有分析数据都可见。如果您看到一条消息,上面写着“Advanced profiling is unavailable for the selected process”,则需要启用高级分析才能看到以下内容:
在Android 8.0及更高版本上,高级分析在可调试的应用程序上始终开启。
你在Memory Profiler顶部看到的数字基于您的应用通过Android 系统提交的所有私有内存页面。此计数不包括与系统或其他应用程序共享的页面。
内存计数中的类别如下:
与之前Android Monitor中内存工具的计数相比,新的Memory Profiler以不同的方式记录您的内存。因而,内存使用率看起来会更高了。Memory Profiler监视一些额外的类别,这些类别增加了总的内存。但是如果您只关心Java堆内存,那么“Java”数值应该与前一个工具中的值类似。但是Java数值可能与您在Android Monitor中看到的不完全匹配,新数值统计了自从Zygote派生以来应用程序的Java堆分配的所有物理页面。因此,它提供了应用程序实际使用的物理内存量的精确表示。
内存分配向您展示了内存中的每个Java对象和JNI引用是如何分配。具体来说,Memory Profiler可以向您显示以下有关对象分配的信息:
如果您的设备运行的是Android 8.0或更高版本,您可以随时查看对象分配,如下所示:在时间轴中拖动以选择要查看分配的区域。不需要开始录制会话,因为Android8.0及更高版本包含一个设备内置分析工具,可以不断跟踪应用程序的分配。详细内容可以参考视频: 高版本查看内存分配
如果您的设备运行的是Android 7.1或更低版本,请单击Memory Profiler工具栏中的 Record memory allocations 图标。录制时,Memory Profiler会跟踪应用程序中发生的所有分配。完成后,单击 Stop recording 图标以查看分配。详细内容可以参考视频: 低版本查看内存分配
选择时间线的某个区域后(或在使用运行Android7.1或更低版本的设备完成录制会话时),已分配对象的列表将显示在时间线下方,按类名分组并按堆计数排序。要检查分配记录,请执行以下步骤:
您可以使用已分配对象列表上方的两个菜单来选择要检查的堆以及如何组织数据。
从左侧的菜单中,选择要检查的堆:
从右侧的菜单中,选择如何组织分配:
为了提高分析时的应用程序性能,默认情况下,内存探查器定期对内存分配进行采样。在运行API级别26或更高级别的设备上测试时,可以使用“分配跟踪”下拉列表更改此行为。可用选项如下:
完全:捕获内存中的所有对象分配。这是Android Studio 3.2和更早版本中的默认行为。如果您的应用程序分配了很多对象,那么在分析时,您可能会看到应用程序的速度明显减慢。
采样:定期采样内存中的对象分配。这是默认选项,在分析时对应用程序性能的影响较小。在短时间内分配大量对象的应用程序可能仍然会显示出明显的减速。
关闭:停止跟踪应用程序的内存分配。
注意:默认情况下,Android Studio在执行CPU录制时停止跟踪实时分配,并在CPU录制完成后将其重新打开。您可以在“CPU记录配置”对话框中更改此行为。
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引用的分配和释放的位置,如下图所示。
要检查应用程序JNI代码的内存分配,必须将应用程序部署到运行Android 8.0或更高版本的设备上。
堆信息能显示在抓取堆信息时应用程序中的哪些对象正在使用内存。特别是在长时间的用户会话之后,通过分析堆信息中是否存在您认为不应该存在的对象,可以用来帮助识别内存泄漏。抓取堆信息后,你可以查看以下内容:
要抓取堆信息,请单击Memory Profiler工具栏中的 Dump Java heap 图标。在抓取期间,Java内存量可能会临时增加。这是正常的,因为堆抓取发生在与应用程序相同的进程中,需要一些内存来收集数据。堆信息在内存时间轴的下方,显示堆中所有类的类型,如下图所示。
如果需要更精确地了解堆的抓取时间,可以通过调用dumpHprofData()在应用程序代码的关键点抓取堆信息。
在类列表中,可以看到以下信息:
您可以使用已分配对象列表上方的两个菜单来选择要检查的堆信息以及如何组织数据。
从左侧的菜单中,选择要检查的堆:
从右侧的菜单中,选择如何组织分配:
默认情况下,列表按 Retained Size 列排序。若要按其他列中的值排序,请单击该列的标题。
单击类名打开右侧的I Instance View 窗口(如下图所示),每个列出的实例包括以下内容:
要检查堆信息,请执行以下步骤:
在堆信息中,注意以下情况可能导致的内存泄漏:
捕获堆信息后,只有在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时,您应该给应用程序代码增加压力,试图去暴露内存泄漏。引发应用程序内存泄漏的一种方法是在检查堆之前让它运行一段时间,泄漏可能会逐渐汇集到堆中分配的顶部。但是当泄漏越小时,应用程序就需要运行越长时间,再进行泄漏检查。
您还可以通过以下方式之一触发内存泄漏:
参考文档:
Android Developers: memory profiler