Memory Profiler是Android Profiler中的一个组件,它能够帮助您识别内存泄漏和可能致使卡顿、冻结甚至应用程序崩溃的内存抖动。它显示一个应用程序内存使用的实时图表,并容许你抓取堆栈信息、强行垃圾收集和跟踪内存分配。html
要打开Memory Profiler,请执行如下步骤:android
Android提供了一个托管内存环境,当它肯定应用程序再也不使用某些对象时,垃圾收集器会将未使用的内存释放回堆中。Android寻找未使用内存的方式正在不断改进,但在全部Android版本中,系统必须短暂暂停代码。大多数时候,停顿是不被感知的。可是,若是应用程序分配内存的速度快于系统回收内存的速度,则应用程序可能会发生延迟,等待回收器释放足够的内存以知足分配。延迟可能致使应用程序发生跳帧并致使明显变慢。正则表达式
即便你的应用程序并无表现出缓慢,但若是它泄露了内存,即便运行在后台也能够占用内存。此行为会致使垃圾回收事件被强制执行,从而下降系统的其他内存性能。最后,系统可能被迫终止应用程序进程以回收内存。所以当用户返回到此应用程序时,它必须彻底从新启动。api
为了帮助防止这些问题,您应该经过如下操做使用Memory Profiler进行检查:浏览器
当您第一次打开Memory Profiler时,您将看到应用程序的内存使用的详细时间轴,和可使用的内存工具包括强制垃圾回收、抓取堆信息和记录内存分配。缓存
如上图所示,Memory Profiler的默认视图包括如下内容:session
内存使用时间轴,包括如下内容:app
可是,若是您使用的是运行Android 7.1或更低版本的设备,默认状况下并不是全部分析数据均可见。若是您看到一条消息,上面写着“Advanced profiling is unavailable for the selected process”,则须要启用高级分析才能看到如下内容:框架
在Android 8.0及更高版本上,高级分析在可调试的应用程序上始终开启。jvm
你在Memory Profiler顶部看到的数字基于您的应用经过Android 系统提交的全部私有内存页面。此计数不包括与系统或其余应用程序共享的页面。
内存计数中的类别以下:
与以前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或更高版本,您能够随时查看对象分配,以下所示:在时间轴中拖动以选择要查看分配的区域。不须要开始录制会话,由于Android8.0及更高版本包含一个设备内置分析工具,能够不断跟踪应用程序的分配。详细内容能够参考视频:高版本查看内存分配
若是您的设备运行的是Android 7.1或更低版本,请单击Memory Profiler工具栏中的Record memory allocations图标。录制时,Memory Profiler会跟踪应用程序中发生的全部分配。完成后,单击Stop recording图标以查看分配。详细内容能够参考视频:低版本查看内存分配
选择时间线的某个区域后(或在使用运行Android7.1或更低版本的设备完成录制会话时),已分配对象的列表将显示在时间线下方,按类名分组并按堆计数排序。
注意:在 Android 7.1 及更低版本上,您最多能够记录 65535 个分配。若是您的记录会话超出此限制,则记录中仅保存最新的 65535 个分配。(在 Android 8.0 及更高版本上,则没有实际的限制。)
要检查分配记录,请执行如下步骤:
您可使用已分配对象列表上方的两个菜单来选择要检查的堆以及如何组织数据。
从左侧的菜单中,选择要检查的堆:
从右侧的菜单中,选择如何组织分配:
为了提升分析时的应用程序性能,默认状况下,内存探查器按期对内存分配进行采样。在运行API级别26或更高级别的设备上测试时,可使用Allocation Tracking下拉列表更改此行为。可用选项以下:
注意:默认状况下,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列排序。若要按其余列中的值排序,请单击该列的标题。
单击类名打开右侧的IInstance View窗口(以下图所示),每一个列出的实例包括如下内容:
注意:默认状况下,堆信息不会向您显示每一个已分配对象的堆栈轨迹。要获取堆栈轨迹,在点击 Dump Java heap 以前,您必须先开始记录内存分配。而后,您能够在 Instance View 中选择一个实例,并查看 References 标签旁边的 Call Stack 标签,以下图所示。不过,在您开始记录分配以前,可能已分配一些对象,所以不会显示这些对象的调用堆栈。在包含调用堆栈的实例在图标上会有一个“堆栈”标志表示。(遗憾的是,因为堆栈轨迹须要您执行分配记录,所以您目前没法在 Android 8.0 上查看堆信息的堆栈轨迹。)
要检查堆信息,请执行如下步骤:
在堆信息中,注意如下状况可能致使的内存泄漏:
捕获堆信息后,只有在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时,您应该给应用程序代码增长压力,试图去暴露内存泄漏。引起应用程序内存泄漏的一种方法是在检查堆以前让它运行一段时间,泄漏可能会逐渐聚集到堆中分配的顶部。可是当泄漏越小时,应用程序就须要运行越长时间,再进行泄漏检查。
您还能够经过如下方式之一触发内存泄漏:
参考文档: