2.AS内存分析
参考
http://www.jianshu.com/p/216b03c22bb8?nomobile=yes
http://www.jianshu.com/p/c49f778e7acf
https://developer.android.com/studio/profile/android-profiler?hl=zh_cn
https://developer.android.com/studio/profile/memory-profiler.html?hl=zh_cn#capture-heap-dump
通过Android monitor进行内存分析
首先需要明白一个概念, 内存泄露就是指,本应该回收的内存,还驻留在内存中。
一般情况下,高密度的手机,一个页面大概就会消耗20M内存,如果发现退出界面,程序内存迟迟不降低的话,可能就发生了严重的内存泄露。我们可以反复进入该界面,然后点击dump java heap 这个按钮,然后Android Studio就开始干活了。
dump成功后会自动打开 hprof文件,文件以Snapshot+时间来命名,但这个并不是标准的hprof文件,要使用mat分析的话需要右键文件进行转换。
hprof文件会保存在项目根目录下的captures里。但是要打开则需要在:
打开hprof文件后:
名称 |
描述 |
Class name |
类名 |
Total Count |
该类的实例总数 |
Heap Count |
所选择的堆中该类的实例的数量 |
Sizeof |
单个实例所占空间大小(如果每个实例所占空间大小不一样则显示0) |
Shallow Size |
堆里所有实例大小总和(Heap Count * Sizeof) |
Retained Size |
该类所有实例所支配的内存大小 |
Instance |
具体的实例 |
Reference Tree |
所选实例的引用,以及指向该引用的引用。 |
Depth |
GC根节点到所选实例的最短路径的深度 |
Shallow Size |
所选实例的大小 |
Dominating Size |
所选实例所支配的内存大小 |
追踪内存分配
如果我们想了解内存分配更详细的情况,可以使用Allocation Traker来查看内存到底被什么占用了。用法很简单:
点击追踪内存分配
点一下是追踪, 再点一下是停止追踪, 停止追踪后 .alloc文件会自动打开,打开后界面如下:
当你想查看某个方法的源码时,右键选择的方法,点击Jump to source就可以了
查询方法执行的时间
如果我们要观测方法执行的时间,就需要来到CPU界面
点击Start Method Tracking, 一段时间后再点击一次, trace文件被自动打开,
结果
非独占时间: 某函数占用的CPU时间,包含内部调用其它函数的CPU时间。
独占时间: 某函数占用CPU时间,但不含内部调用其它函数所占用的CPU时间。
新版Android Profiler
在新版中叫Android Profiler ,使用方法大致都一样。
Android Profiler 与 Android 5.0(API 级别 21)及更高版本兼容。
这个界面有cpu,内存,网络等数据,可以点开某个模块。
1 Android Profiler 显示当前正在分析的进程和设备。
2 在 Sessions 窗格中,选择要查看的会话,或启动一个新的分析会话。
3 使用缩放按钮控制要查看时间轴范围,或使用 Attach to live 按钮跳转到实时更新。
4 事件时间轴显示与用户输入相关的事件,包括键盘 Activity、音量控制更改和屏幕旋转。
5 共享时间轴视图,包括 CPU、内存、网络和耗电量图表。
此共享时间轴视图只显示时间轴图表。要使用详细分析工具,请点击与您要检查的性能数据对应的图表。例如,要使用相关工具检查堆数据和跟踪内存分配,请点击 Memory 图表。
并非所有分析数据在默认情况下都可见。如果您看到一条消息,显示"Advanced profiling is unavailable for the selected process",您可以在运行配置中启用高级分析以查看其他数据。
会话
您可以将分析器数据另存为会话,这些会话将一直保留,直到您退出 Android Studio。
通过在多个会话中记录分析信息并在它们之间进行切换,您可以比较各种场景中的资源使用情况。
-
要启动一个新的会话,请点击 Start a new profiling session 按钮+,然后从出现的下拉菜单中选择一个应用进程。
在您记录函数数据或捕获堆转储后,Android Studio 会将相应数据(以及您应用的网络 Activity)作为单独的条目添加到当前会话。
-
要停止向当前会话添加数据,请点击 Stop the current profiling session 图标 。
-
要导入之前运行 Android Studio 时导出的跟踪记录,请点击 Start new profiler session 图标 ,然后选择 Load from file。
启用高级分析
要在运行搭载 Android 7.1 或更低版本的设备时向您显示高级分析数据,Android Studio 必须将监控逻辑注入您编译的应用。
高级分析提供的功能包括:
-
所有分析器窗口中的事件时间轴
-
Memory Profiler 中已分配对象的数量
-
Memory Profiler 中的垃圾回收事件
-
Network Profiler 中有关所有已传输文件的详细信息
注意:如果您的设备搭载的是 Android 8.0 或更高版本,则默认情况下可以使用这些功能。
要启用高级分析功能,请按以下步骤操作:
-
依次选择 Run > Edit Configurations。
-
在左侧窗格中选择您的应用模块。
-
点击 Profiling 标签,然后勾选 Enable advanced profiling。
-
重新编译并运行您的应用。
高级分析配置会使编译过程变慢,所以仅在您想要开始分析您的应用时,才应启用该配置。
注意:高级分析功能不可用于原生代码native code。如果您的应用是纯原生应用native app(不含 Java Activity 类),则不可使用高级分析功能。如果您的应用使用 JNI,则可使用部分高级分析功能(如事件时间轴、垃圾回收事件、Java 分配对象和基于 Java 的网络 Activity),但不能检测基于原生代码的分配和网络 Activity。
使用 Memory Profiler 查看 Java 堆和内存分配
https://developer.android.com/studio/profile/memory-profiler?hl=zh_cn
Memory Profiler 是 Android Profiler 中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄漏和内存抖动。它显示一个应用内存使用量的实时图表,让您可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。
Memory Profiler 概览
当您首次打开 Memory Profiler 时,您将看到一条表示应用内存使用量的详细时间轴,并可使用各种工具来强制执行垃圾回收、捕获堆转储以及记录内存分配。
图 1. Memory Profiler
如图 1 所示,Memory Profiler 的默认视图包括以下各项:
-
用于强制执行垃圾回收事件的按钮。
注意:只有在连接到搭载 Android 7.1(API 级别 25)或更低版本的设备时,才会在此按钮右侧显示record memory allocations的按钮。
-
一个下拉菜单,用于指定分析器捕获内存分配的频率。选择适当的选项可帮助您在分析时提高应用性能。
-
用于放大/缩小时间轴的按钮。
-
用于跳转到实时内存数据的按钮。
-
事件时间轴,显示活动状态、用户输入事件和屏幕旋转事件。
-
内存使用量时间轴,它包括以下内容:
-
一个堆叠图表,显示每个内存类别当前使用多少内存,如左侧的 y 轴以及顶部的彩色键所示。
-
一条虚线,表示分配的对象数,如右侧的 y 轴所示。
-
垃圾桶图标,每个垃圾回收事件的图标。
不过,如果您使用的是搭载 Android 7.1 或更低版本的设备,则并非所有分析数据在默认情况下都可见。如果您看到一条消息,显示"Advanced profiling is unavailable for the selected process",您需要启用高级分析才能看到以下内容:
-
事件时间轴
-
分配的对象数
-
垃圾回收事件
启动高级分析看上边。
在 Android 8.0 及更高版本上,始终为可调试应用启用高级分析。
如何计算内存
您在 Memory Profiler 顶部看到的数字(图 2)基于您的应用根据 Android 系统机制所提交的所有私有内存页面。此计数不包含与系统或其他应用共享的页面。
图 2. Memory Profiler 顶部的内存计数图例
内存计数中的类别如下:
-
Java:从 Java 或 Kotlin 代码分配的对象的内存。
-
Native:从 C 或 C++ 代码分配的对象的内存。
即使您的应用中不使用 C++,您也可能会看到此处使用的一些原生内存,因为 Android 框架使用原生内存代表您处理各种任务,如处理图像资源和其他图形时,即使您编写的代码采用 Java 或 Kotlin 语言。
-
Graphics:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。(请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)
-
Stack:您的应用中的原生堆栈和 Java 堆栈使用的内存。这通常与您的应用运行多少线程有关。
-
Code:您的应用用于处理代码和资源(如 dex 字节码、经过优化或编译的 dex 代码、.so 库和字体)的内存。
-
Others:您的应用使用的系统不确定如何分类的内存。
-
Allocated:您的应用分配的 Java/Kotlin 对象数。此数字没有计入 C 或 C++ 中分配的对象。
如果连接到搭载 Android 7.1 及更低版本的设备,只有在 Memory Profiler 连接到您运行的应用时,才开始此分配计数。因此,您开始分析之前分配的任何对象都不会被计入。不过,Android 8.0 及更高版本附带一个设备内置分析工具,该工具可跟踪所有分配,因此,在 Android 8.0 及更高版本上,此数字始终表示您的应用中待处理的 Java 对象总数。
与以前的 Android Monitor 工具中的内存计数相比,新的 Memory Profiler 以不同的方式记录您的内存,因此,您的内存使用量现在看上去可能会更高些。Memory Profiler 监控的类别更多,这会增加总的内存使用量,但如果您仅关心 Java 堆内存,则"Java"项的数字应与以前工具中的数值相似。然而,Java 数字可能与您在 Android Monitor 中看到的数字并非完全相同,这是因为新数字计入了自应用的 Java 堆从 Zygote 派生以来为其分配的所有物理内存页面。因此,它可以准确反映您的应用实际使用了多少物理内存。
注意:使用搭载 Android 8.0(API 级别 26)及更高版本的设备时,Memory Profiler 还会显示应用中的一些误报的原生内存使用量,而这些内存实际上是分析工具使用的。对于大约 100000 个对象,最多会使报告的内存使用量增加 10MB。在 IDE 的未来版本中,这些数字将从您的数据中过滤掉。
查看内存分配
内存分配为您显示内存中的每个 Java 对象和 JNI 引用是如何分配的。
具体而言,Memory Profiler 可为您显示有关对象分配的以下信息:
-
分配了哪些类型的对象以及它们使用多少空间。
-
每个分配的堆栈轨迹,包括在哪个线程中。
-
对象在何时被取消分配(仅当使用搭载 Android 8.0 或更高版本的设备时)。
如果您的设备搭载的是 Android 8.0 或更高版本,您可以随时查看对象分配,具体操作步骤如下:在时间轴上拖动以选择要查看哪个区域的分配(如视频 1 中所示)。不需要开始记录会话,因为 Android 8.0 及更高版本附带设备内置分析工具,可持续跟踪您的应用分配。
如果您的设备搭载的是 Android 7.1 或更低版本,请点击 Memory Profiler 工具栏中的 Record memory allocations 图标 。记录时,Memory Profiler 会跟踪您的应用中发生的所有分配。完成后,请点击 Stop recording 图标 (同一按钮,请观看视频 2)以查看分配。
选择时间轴的某个区域后(或者使用搭载 Android 7.1 或更低版本的设备完成记录会话后),已分配对象的列表将显示在时间轴下方,按类名称进行分组,并按其堆计数排序。
注意:在 Android 7.1 及更低版本上,您最多可以记录 65535 个分配。如果您的记录会话超出此限制,则记录中仅保存最新的 65535 个分配。(在 Android 8.0 及更高版本上,则没有实际的限制。)
要检查分配记录,请按以下步骤操作:
-
浏览列表以查找堆计数异常大且可能存在泄漏的对象。为帮助查找已知类,点击 Class Name 列标题以按字母顺序排序。然后,点击一个类名称。此时右侧将出现 Instance View 窗格,显示该类的每个实例,如下图所示。
或者,您也可以快速找到对象,方法是点击 Filter 图标进行搜索具体的类。
-
在 Instance View 窗格中,点击一个实例。此时下方将出现 Call Stack 标签,显示该实例被分配到何处以及哪个线程中。
-
在 Call Stack 标签中,右键点击任意行并选择 Jump to Source,以在编辑器中打开该代码。
您可以使用已分配对象列表上方的两个菜单来选择要检查的堆以及如何组织数据。
从左侧的菜单中,选择要检查的堆:
-
default heap:当系统未指定堆时。
-
image heap:系统启动映像,包含启动期间预加载的类。此处的分配保证绝不会移动或消失。
-
zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的。
-
app heap:您的应用在其中分配内存的主堆。
-
JNI heap:显示 Java 原生接口 (JNI) 引用被分配和释放到什么位置的堆。
从右侧的菜单中,选择如何安排分配:
-
Arrange by class:根据类名称对所有分配进行分组。这是默认选项。
-
Arrange by package:根据软件包名称对所有分配进行分组。
-
Arrange by callstack:将所有分配分组到其对应的调用堆栈。
在分析时提高应用性能
为了在分析时提高应用性能,Memory Profiler 在默认情况下会定期对内存分配进行采样。在运行 API 级别 26 或更高级别的设备上进行测试时,您可以使用 Allocation Tracking 下拉菜单来更改此行为。
可用选项如下:
-
Full:捕获内存中的所有对象分配。这是 Android Studio 3.2 及更低版本中的默认行为。如果您有一个分配了大量对象的应用,则可能会在分析时观察到应用的运行速度明显减慢。
-
Sampled:定期对内存中的对象分配进行采样。这是默认选项,在分析时对应用性能的影响较小。在短时间内分配大量对象的应用仍可能会表现出明显的速度减慢。
-
Off:停止跟踪应用的内存分配。
注意:默认情况下,Android Studio 会在执行 CPU 记录时停止跟踪实时分配,并在 CPU 记录完成后重新开启该功能。您可以在 CPU 记录配置对话框中更改此行为。
分析内存的技巧
使用 Memory Profiler 时,您应对应用代码施加压力并尝试强制内存泄漏。在应用中引发内存泄漏的一种方式是,先让其运行一段时间,然后再检查堆。泄漏在堆中可能逐渐汇聚到分配顶部。不过,泄漏越小,为了看到泄漏而需要运行应用的时间就越长。
您还可以通过以下某种方式来触发内存泄漏:
-
在不同的 Activity 状态下,在您的应用与其他应用之间切换(导航到主屏幕,然后返回到您的应用)。
通过mat进行内存分析
下载地址 http://eclipse.org/mat/downloads.php
通过Android Studio自带的界面,查看内存泄露还不是很智能,我们可以借助第三方工具,常见的工具就是MAT了 ,这里我们需要下载独立版的MAT.
这里需要提醒大家的是,MAT并不会准确地告诉我们哪里发生了内存泄漏,而是会提供一大堆的数据和线索,我们需要自己去分析这些数据来去判断到底是不是真的发生了内存泄漏。
Android Studio dump成功后会自动打开 hprof文件,文件以Snapshot+时间来命名,但这个并不是标准的hprof文件,要使用mat分析的话需要右键文件进行转换。
hprof文件会保存在项目根目录下的captures里。
然后用MAT打开导出的hprof(File->Open heap dump) MAT会帮我们分析内存泄露的原因
下图是MAT一开始打开的界面
有很多功能,但一般会使用Histogram,Dominator Tree,Leak Suspects。
-
Histogram:列出每个类有多少个对象,
-
Dominator Tree:列出大对象,
-
Leak Suspects:自带的一个内存泄漏检查工具,可以帮我们分析出可能的导致内存泄漏的对象。
-
搜索
Histogram
列出每个类有多少个对象
Dominator Tree
列出大对象
Leak Suspects
自带的一个内存泄漏检查工具,可以帮我们分析出可能的导致内存泄漏的对象,
自动分析内存泄露的原因
搜索
MAT也支持搜索,所以可以直接搜索你认为的最可能出现内存泄漏的类。
一般内存泄露都是activity,所以可以直接查找activity分析即可。
-
点击OQL图标
-
在窗口输入select * from instanceof android.app.Activity并按Ctrl + F5或者!按钮
-
奇迹出现了,现在你发现泄露了许多的activity
-
这个真是相当的不容乐观,我们来分析一下为什么GC没有回收它
点击一个activity对象-》右键选中Path to GC roots-》然后选择exclude weak/soft references(排除软弱引用),
在打开的新窗口中,你可以发现,你的Activity是被this$0所引用的,它实际上是匿名类对当前类的引用。this$0又被callback所引用,接着它又被Message中一串的next所引用,最后到主线程才结束。任何情况下你在class中创建非静态内部类,内部类会(自动)拥有对当前类的一个强引用。
我们如何判断可能有问题的方法?
通过方法的调用次数和独占时间来查看,通常判断方法是:
-
如果方法调用次数不多,但每次调用却需要花费很长的时间的函数,可能会有问题。
-
如果自身占用时间不长,但调用却非常频繁的函数也可能会有问题。