Android 内存分析

Android 内存分析

使用内存性能分析器查看应用的内存使用情况

内存性能分析器是 Android Profiler 中的一个组件,可帮助识别可能会导致应用卡顿、冻结甚至崩溃的内存泄漏和内存抖动。它显示一个应用内存使用量的实时图表,可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。

如需打开内存性能分析器,请按以下步骤操作:

  1. 依次点击 View > Tool Windows > Profiler
  2. 从 Android Profiler 工具栏中选择要分析的设备和应用进程。
  3. 点击 MEMORY 时间轴上的任意位置以打开内存性能分析器。

或者,可以从命令行使用 dumpsys检查应用内存,还可以在 logcat 中查看 GC 事件。

内存性能分析器概览

首次打开内存性能分析器时,将看到一条表示应用内存使用量的详细时间轴,并可使用各种工具强制执行垃圾回收、捕获堆转储以及记录内存分配。

memory-profiler-callouts_2x.png

内存性能分析器的默认视图包括以下各项:

  1. 用于强制执行垃圾回收事件的按钮。
  2. 用于捕获堆转储的按钮。
    注意:只有在连接到搭载 Android 7.1(API 级别 25)或更低版本的设备时,系统才会在堆转储按钮右侧显示用于记录内存分配情况的按钮。
  3. 用于指定性能分析器多久捕获一次内存分配的下拉菜单。
  4. 用于缩放时间轴的按钮。
  5. 用于跳转到实时内存数据的按钮。
  6. 事件时间轴,显示活动状态、用户输入事件和屏幕旋转事件。
  7. 内存使用量时间轴,它会显示以下内容:
  • 一个堆叠图表,显示每个内存类别当前使用多少内存,如左侧的 y 轴以及顶部的彩色键所示。
  • 一条虚线,表示分配的对象数,如右侧的 y 轴所示。
  • 每个垃圾回收事件的图标。
内存计算方式

内存计数中的类别如下:

  • Java:从 Java 或 Kotlin 代码分配的对象的内存。
  • Native:从 C 或 C++ 代码分配的对象的内存。
    即使应用中不使用 C++,您也可能会看到此处使用了一些原生内存,因为即使您编写的代码采用 Java 或 Kotlin 语言,Android 框架仍使用原生内存代表您处理各种任务,如处理图像资源和其他图形。
  • Graphics:图形缓冲区队列为向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。(请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)
  • Stack:应用中的原生堆栈和 Java 堆栈使用的内存。这通常与应用运行多少线程有关。
  • Code:应用用于处理代码和资源(如 dex 字节码、经过优化或编译的 dex 代码、.so 库和字体)的内存。
  • Others:应用使用的系统不确定如何分类的内存。
  • Allocated:应用分配的 Java/Kotlin 对象数。此数字没有计入 C 或 C++ 中分配的对象。

查看内存分配

要检查内存分配记录,可以按以下步骤操作:

  1. 浏览列表以查找堆计数异常大且可能存在泄漏的对象。 点击 Class Name 列标题以按字母顺序排序。 然后点击一个类名称。 此时在右侧将出现 Instance View 窗格,显示该类的每个实例。
  2. Instance View 窗格中,点击一个实例。 此时下方将出现 Call Stack 标签,显示该实例被分配到何处以及哪个线程中
  3. Call Stack 标签中,点击任意行以在编辑器中跳转到该代码

10992781-4612f777b3278125.webp

默认情况下,左侧的分配列表按类名称排列。 在列表顶部,你可以使用右侧的下拉列表在以下排列方式之间进行切换:

  • Arrange by class:基于类名称对所有分配进行分组
  • Arrange by package:基于软件包名称对所有分配进行分组
  • Arrange by callstack:将所有分配分组到其对应的调用堆栈

捕获堆转储

堆转储显示在您捕获堆转储时您的应用中哪些对象正在使用内存

要捕获堆转储,在 Memory Profiler 工具栏中点击 Dump Java heap。 在转储堆期间,Java 内存量可能会暂时增加。因为堆转储与您的应用发生在同一进程中,并需要一些内存来收集数据。

要检查堆信息,请按以下步骤操作:

  1. 浏览列表以查找堆计数异常大且可能存在泄漏的对象。 为帮助查找已知类,点击 Class Name 列标题以按字母顺序排序。 然后点击一个类名称。 此时在右侧将出现 Instance View 窗格,显示该类的每个实例,如图 5 中所示
  2. Instance View窗格中,点击一个实例。此时下方将出现References,显示该对象的每个引用
  3. References 标签中,如果您发现某个引用可能在泄漏内存,则右键点击它并选择 Go to Instance

在堆转储中,请注意由下列任意情况引起的内存泄漏:

  • 长时间引用 ActivityContextViewDrawable 和其他对象,可能会保持对 ActivityContext 容器的引用
  • 可以保持 Activity 实例的非静态内部类,如 Runnable
  • 对象保持时间超出所需时间的缓存

10992781-2ecdfec25119d2e4.webp

在类列表中,可以查看以下信息:

  • Heap Count:堆中的实例数
  • Shallow Size:此堆中所有实例的总大小(以字节为单位)
  • Retained Size:为此类的所有实例而保留的内存总大小(以字节为单位)

在类列表顶部,你可以使用左侧下拉列表在以下堆转储之间进行切换:

  • Default heap:系统未指定堆时
  • App heap:应用在其中分配内存的主堆
  • Image heap:系统启动映像,包含启动期间预加载的类。 此处的分配保证绝不会移动或消失
  • Zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的

Instance View 中,每个实例都包含以下信息:

  • Depth:从任意 GC 根到所选实例的最短 hop 数
  • Shallow Size:此实例的大小
  • Retained Size:此实例支配的内存大小

MAT使用

MAT(Memory Analyzer Tool),一个基于 Eclipse 的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。

获取HPROF文件

HPROF文件是MAT能识别的文件,HPROF文件存储的是特定时间点,java进程的内存快照。有不同的格式来存储这些数据,总的来说包含了快照被触发时java对象和类在heap中的情况。由于快照只是一瞬间的事情,所以heap dump中无法包含一个对象在何时、何地(哪个方法中)被分配这样的信息。在 Android Studio 中,Sessions 窗格中每个 Heap Dump 条目的右侧都有一个 Export Heap Dump 按钮。在随即显示的 Export As 对话框中,使用 .hprof 文件扩展名保存文件。将 HPROF 文件从 Android 格式转换为 Java SE HPROF 格式。 使用 android_sdk/platform-tools/ 目录中提供的 hprof-conv 工具执行此操作。例如:

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

转换过后的.hprof文件即可使用MAT工具打开了。打开经过转换的hprof文件:

1639985758(1).jpg

选择OverView界面:

1639985846(1).png

需要关注的是下面的Actions区域

  • Histogram:列出内存中的对象,对象的个数以及大小

1639985939(1).png

  • Dominator Tree:列出最大的对象以及其依赖存活的Object (大小是以Retained Heap为标准排序的)

1639985994(1).png

  • Top Consumers : 通过图形列出最大的object

1639986040(1).png

  • Duplicate Class:通过MAT自动分析泄漏的原因

MAT中的一些有用的视图

Thread OvewView查看这个应用的Thread信息:

1639986320(1).jpg

Path to GC Root

在Histogram或者Domiantor Tree的某一个条目上,右键可以查看其GC Root Path:

从最强到最弱,不同的引用(可到达性)级别反映了对象的生命周期。

  • Strong Ref(强引用):通常我们编写的代码都是Strong Ref,于此对应的是强可达性,只有去掉强可达,对象才被回收。
  • Soft Ref(软引用):对应软可达性,只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。一般可用来实现缓存,通过java.lang.ref.SoftReference类实现。
  • Weak Ref(弱引用):比Soft Ref更弱,当发现不存在Strong Ref时,立刻回收对象而不必等到内存吃紧的时候。通过java.lang.ref.WeakReference和java.util.WeakHashMap类实现。
  • Phantom Ref(虚引用):根本不会在内存中保持任何对象,你只能使用Phantom Ref本身。一般用于在进入finalize()方法后进行特殊的清理过程,通过 java.lang.ref.PhantomReference实现。

点击Path To GC Roots –> with all references

1639986564(1).png

这些选项的具体含义则可以通过右键中的Search Queries这个选项进行搜索和查看。

1639986901(1).png

常用的:

  • List objects -> with incoming references:查看这个对象持有的外部对象引用
  • List objects -> with outcoming references:查看这个对象被哪些外部对象引用
  • Path To GC Roots -> exclude all phantim/weak/soft etc. references:查看这个对象的GC Root,不包含虚、弱引用、软引用,剩下的就是强引用。从GC上说,除了强引用外,其他的引用在JVM需要的情况下是都可以 被GC掉的,如果一个对象始终无法被GC,就是因为强引用的存在,从而导致在GC的过程中一直得不到回收,因此就内存溢出了。
  • Path To GC Roots -> exclude weak/soft references:查看这个对象的GC Root,不含弱引用和软引用所有的引用.
  • Merge Shortest path to GC root:找到从GC根节点到一个对象或一组对象的共同路径
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值