You need to enable JavaScript to run this app.
导航

内存优化

最近更新时间2023.09.12 11:29:52

首次发布时间2021.06.18 16:25:25

内存优化包括OOM趋势、泄漏分析、大对象和单设备内存详情,可以帮助您更好的进行内存优化。

前提条件

OOM趋势

在OOM趋势中,提供了内存泄漏的指标分析。除了核心指标OOM次数、OOM率、影响用户数、影响用户比例,还提供进一步分析OOM的扩展指标,如:App占用内存大小、App占用内存比例。

筛选维度

您可以通过以下筛选条件进行数据筛选。

  • Android系统支持的筛选维度:
    时间、设备ID、User ID、系统版本、APP版本、APP小版本号、机型、OOM类型、进程名、APM SDK版本、下载渠道、PV自定义维度。

    说明

    OOM类型包括:

    • java:Java异常,java.lang.OutOfMemoryError。
    • native:发生Native崩溃或者ANR时,32位应用在32位设备上虚拟内存超过2.8G,或者32位应用在64位设备上虚拟内存超过3.8G。
    • launch:启动阶段的Java OOM。
  • iOS系统支持的筛选维度:
    时间、设备ID、User ID、越狱状态、系统版本、APP版本、APP小版本号、机型、APM SDK版本、下载渠道、PV自定义维度。

指标大盘

OOM趋势图展示了筛选条件下OOM指标的趋势。指标数据的右侧可以查看与上一周期相比OOM的数据变化。
图片

  • 支持添加自定义维度的过滤条件,选择维度、维度与取值的关系以及取值。维度与取值的关系支持配置为=和≠。
  • 单击下载按钮,可以下载.png文件类型的数据概览。
  • 单击查看崩溃详情,可以跳转到崩溃趋势页面查看具体的OOM崩溃趋势、分布和崩溃详情。

指标说明:

指标

说明

OOM次数

筛选条件下发生OOM的次数(PV)

OOM率

筛选条件下的OOM次数/筛选条件下的总launch次数

影响用户数

筛选条件下OOM问题影响的用户数(UV)

影响用户比例

筛选条件下发生OOM错误的去重UV数/筛选条件下的去重总UV数

扩展指标

除了大盘中提供的默认指标外,OOM还支持扩展指标。

iOS端

上报字段

指标名称

app_memory_rate

APP占用内存比例

app_memory

APP占用内存大小

Android端

上报字段

指标名称

获取方式

total_pss_background

物理内存(后台)

Debug.MemoryInfo.getTotalPss()

total_pss_foreground

物理内存(前台)

Debug.MemoryInfo.getTotalPss()

java_heap_background

Java使用内存(后台)

Runtime. getRuntime ().totalMemory()-Runtime. getRuntime ().freeMemory()

java_heap_foreground

Java使用内存(前台)

Runtime. getRuntime ().totalMemory()-Runtime. getRuntime ().freeMemory()

graphics_background

显存(后台)

Debug.MemoryInfo.getMemoryStat("summary.graphics")

graphics_foreground

显存(前台)

Debug.MemoryInfo.getMemoryStat("summary.graphics")

vm_size_background

虚拟内存(后台)

/proc/进程pid/status

vm_size_foreground

虚拟内存(前台)

/proc/进程pid/status

java_heap_background_used_rate

Java内存使用率(后台)

java_heap_background/Runtime. getRuntime ().maxMemory()

java_heap_foreground_used_rate

Java内存使用率(前台)

java_heap_foreground/Runtime. getRuntime ().maxMemory()

dalvik_pss_background

Java物理内存使用(后台)

Debug.MemoryInfo.dalvikPss

dalvik_pss_foreground

Java物理内存使用(前台)

Debug.MemoryInfo.dalvikPss

native_pss_background

Native物理内存使用(后台)

Debug.MemoryInfo.nativePss

native_pss_foreground

Native物理内存使用(前台)

Debug.MemoryInfo.nativePss

OOM详情

单击扩展指标名称进入指标详情页面。

指标趋势图

图片

自定义维度分析

图片

泄漏分析

内存泄漏指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费。内存泄漏有时不严重且不易察觉,以至于开发者不知道存在内存泄漏、泄漏积累过多、内存耗尽等情况,最终导致OOM。
泄漏分析中提供了导致泄漏的对象类型列表和详情分析,在这里可以逐一分析那些导致严重内存泄漏的对象。

筛选维度

您可以通过以下筛选条件进行数据筛选:
时间、系统、系统版本、APP版本、机型、APM SDK版本、issue状态、处理人、标签、泄漏详情。

泄漏分析

图片

  • 列表默认展示了泄露说明、起止版本、次数、影响用户数、平均大小、总大小、处理人和状态。
  • 单击设置按钮,可以配置列表中显示的列,包括同比次数、同比影响用户数、同比平均大小、同比总大小。其中,泄露说明不支持取消显示。
  • 单击下载按钮,可以下载前1000条数据,查看以.xlsx文件格式的泄漏数据的详情。
  • 支持根据次数、影响用户数、平均大小、总大小一键正序或者倒序排列。
  • 支持直接分配处理人,以及修改处理状态。
  • 次数和影响用户数列支持查看趋势图。趋势图中最多只展示最近48个点。

泄漏详情

单击泄漏趋势列表中的泄漏说明,进入泄漏详情页面。您可以查询泄漏摘要、详细信息、泄漏趋势、泄漏分布、详细信息、引用链和符号表。

摘要

图片
摘要中可以查看泄漏的发生次数、影响用户数、影响用户比例、最近上报时间、最近发生时间、首次发生时间,还可以查看和设置泄漏的状态、处理人和标签。

泄漏分布

图片
泄漏分布中支持根据不同维度筛选数据,默认按照设备机型、设备系统版本、rom信息、下载渠道筛选数据。您可以单击选择维度自定义分布维度。

泄漏趋势

图片
泄漏趋势中根据筛选条件展示数据。默认按照发生次数查看数据的趋势,您可以切换成按照影响用户数查看数据的趋势。

详细信息

详细信息中可以查看泄漏的异常数据的详细信息。
图片

  • 单击显示自定义维度参数,可以只查看自定义的参数。
  • 单击下载日志文件,可以查看.json文件格式的日志文件,在该日志文件中查看详细信息。
  • 单击单设备内存详情,可以查看当前DID在OOM时具体内存占用情况,包括泄漏和大对象等数据。
  • 单击右上角切换其他泄漏数据的详细信息。
  • 单击设备标识ID或UserID后的追查,可以进入日志查询页面查看该设备标识ID或UserID的日志信息。

引用链

图片
引用链会展示泄露的Activity类和具体GC Root之间的具体引用关系。把引用关系断掉可以解决泄露问题。所有泄露都应该被优化解决。

大对象分析

大对象是指“单个大小超过1000KB的对象”或“个数超过50且内存总大小大于10000KB的类”。大对象是内存占用的主力军,是优化内存过程中重点分析的部分。
大对象列表中提供了占用内存过大的大对象以及数量众多聚合而成的小对象,帮助您更准确地了解内存被哪些对象占用。

筛选维度

您可以通过以下筛选条件进行数据筛选:
时间、系统版本、APP版本、机型、大对象类型、APM SDK版本、issue状态、处理人、标签、卡顿详情。

大对象详情列表

图片

  • 列表默认展示了大对象详情、起止版本、次数、影响用户数、平均大小、总大小、处理人以及状态。
  • 单击设置按钮,可以配置列表中显示的列,包括同比次数、同比影响用户数、同比平均大小、同比总大小。其中,对象详情不支持取消显示。
  • 单击下载按钮,可以下载前10000条数据,查看以.xlsx文件格式的大对象数据的详情。
  • 支持根据次数、影响用户数、平均大小、总大小一键正序或者倒序排列。
  • 支持直接分配处理人,以及修改处理状态。
  • 次数和影响用户数列支持查看趋势图。趋势图中最多只展示最近48个点。

大对象详情

单击大对象分析列表中的大对象详情,进入大对象详情页面。除了可以查询大对象摘要、详细信息、大对象趋势、大对象分布、详情信息、引用链、持有对象和符号表。

摘要

图片
摘要中可以查看大对象的发生次数、影响用户数、影响用户比例、最近上报时间、最近发生时间、首次发生时间,还可以查看和设置大对象的状态、处理人和标签。

大对象分布

图片
大对象分布中支持根据不同维度筛选数据,默认按照设备机型、设备系统版本、rom信息、下载渠道筛选数据。您可以单击选择维度自定义分布维度。

大对象趋势

图片
大对象趋势中根据筛选条件展示数据。默认按照发生次数查看数据的趋势,您可以切换成按照影响用户数查看数据的趋势。

详细信息

详细信息中可以查看大对象的异常数据的详细信息。
图片

  • 单击显示自定义维度参数,可以只查看自定义的参数。
  • 单击下载日志文件,可以查看.json文件格式的日志文件,在该日志文件中查看详细信息。
  • 单击单设备内存详情,可以查看当前DID在OOM时具体内存占用情况,包括泄漏和大对象等数据。
  • 单击右上角切换其他大对象的详细信息。
  • 单击设备标识ID或UserID后的追查,可以进入日志查询页面查看该设备标识ID或UserID的日志信息。

引用链

引用链会展示大对象到具体GC Root的具体引用关系,可以判断引用关系是否合理,是否可以断掉。
图片

持有对象

持有对象会展示大对象具体引用了哪些对象导致自身占内存较大,可以判断是否可以通过优化持有对象来优化内存占用。
图片
例如,图中List缓存对象过多,并且缓存对象过大导致内存占据大,可以通过清除缓存对象来达到优化的目的。

单设备内存详情

在单设备内存详情中,可直接分析单台设备的内存问题。iOS提供了直接精准分析单台设备的内存引用树、支配树、实例等,Android可下载查询其原始数据。

筛选维度

您可以通过以下筛选条件进行数据筛选。

  • Android系统支持的筛选维度:
    时间、系统版本、APP版本、机型、APP小版本、下载渠道。
  • iOS系统支持的筛选维度:
    时间、issue新增状态、前后台、越狱状态、隐藏降级案例、系统版本、APP版本、机型、APP小版本号、下载渠道。

Android界面概览

图片

  • 列表展示Device ID、采集时间、APP版本、APP小版本号、操作系统版本和机型等数据。
  • 支持根据采集时间、APP版本、APP小版本号、操作系统版本、机型进行排序。
  • 单击下载按钮,可以下载前10000条数据,查看以.xlsx文件格式的设备内存信息。
  • 单击操作列的原始数据下载,平台将目标设备的数据打包成zip文件,解压后可以查看.hprof文件内容。
  • 单击DeviceID,展开后可以查看对象泄漏、单个大对象和类大对象等信息。
    图片
    • 在对象泄漏页签,单击对象名称,进入泄漏详情中进行泄漏分析。
    • 在单个大对象页签,单击对象名称,进入大对象详情中进行大对象分析。
    • 在类大对象页签,单击类名称,进入大对象详情中进行大对象分析。

iOS界面概览

图片

  • 列表展示Device ID、采集时间、APP版本、APP小版本号、操作系统版本和机型等数据。
  • 支持根据采集时间、APP版本、APP小版本号、操作系统版本、机型进行排序。
  • 单击下载按钮,可以下载前10000条数据,查看以.xlsx文件格式的设备内存信息。
  • 单击操作列的查看详情,进入内存详情页面查看该设备详细信息。
    图片
    内存详情页面大致分为五块区域,分别是①聚类列表、②实例列表、③基本信息、④引用树和支配树、⑤回溯路径。

聚类列表

聚类列表展示了App内存的总览,内存节点按照类型进行聚合,通过排序等方法可以帮助您快速确定App中内存的大致分布,初步暴露问题。
图片

  • 在搜索框输入类名搜索指定节点,支持模糊匹配。
  • 为了展示的粒度更细,默认按照类名+大小进行聚合展示,可以单击按类名聚合仅按照类名进行聚合展示。
  • 勾选只看泄漏则只展示泄漏的节点。

    说明

    • 如果勾选此选项后,没有节点显示,那么表示没有泄漏的节点类型。
    • 勾选后,左上角节点数和占用内存大小的数据也会发生相应变化。
  • 单击一条节点信息,即可在右侧区域②实例列表中显示此种类型的所有实例信息。

列表中的参数说明:

参数

说明

类名

标识内存节点类型名称。

  • 按类名+大小聚合:显示节点类型名称+节点大小。
  • 按类名聚合:只显示类型名称。

选择不同聚合方式,数量、SHALLOW SIZE、RETAIN SIZE这些列数据都会发生变化。同时,区域②实例列表的显示信息也会发生变化。

数量

此种类型的节点数量。

SHALLOW SIZE

此种类型节点的所有实例占用内存之和。

RETAIN SIZE

此种类型节点的所有实例,及其所支配的节点,占用内存之和(支配定义可以查看附录)。

实例列表

实例列表展示了聚类列表中某个集合的所有节点。通过一些分析手段对一类节点打标签,可以根据用途对节点进行分类,也可以单击某个节点以查看某个具体实例的详细信息。
图片

  • 头部左上角显示的是当前实例列表的节点类型,右上角显示当前实例列表的所有实例占用内存之和(不包含支配节点)。
  • 搜索框中输入祖先(父亲或者父亲的父亲,直到根节点)名称或者具体的指针,可以筛选出有相应祖先的节点。通常使用此功能确定有多少节点同属于一类或某个具体的对象。例如,是不是都在缓存里,是不是都属于某个VC等等。
    注意,此处不支持模糊匹配,且不要包含大小相关的字符串。输入完成后回车,或者单击搜索按钮会出现对话框,例如此处输入__NSCFDictionary。
    图片
    对话框显示了当前__NSCFString类型下,有多少个实例节点,即__NSCFString类型的所有实例,包括泄漏和非泄漏的祖先是__NSCFDictionary类型,单击确定后给这些节点打上标签。
    图片
    双击标签即可删除所有此类标签,例如,双击上图中的 "1" 即可删除所有 "1" 标签。
  • 实例信息里每一行显示了实例的地址,括号里面 "+" 前面显示了实例自身占用内存大小,"+" 后面显示了实例支配的节点内存大小之和。
    单击一条实例信息,即可在下方显示实例的基本信息、引用树和支配树。

基本信息

基本信息展示了一个节点的基本信息,包括节点的大小、地址、类型、是否为泄漏、标签等等。
图片
参数说明:

参数

说明

类名

当前节点的类名,以类名+大小的形式展示。

节点类型

节点所属的类型。
OC/Swift/Vitrual C\+\+对象通过符号化可以得到有意义的信息。

Ptr

节点的地址。

VMType

虚拟内存类型,Heap或者具体的VM类型。

VMSize

内存大小,对于不同类型的节点,该数据有不同的口径。

  • 对于libmalloc管理的内存节点,该大小为malloc最终分配的大小。
  • 对于VM: Stack类型,该大小表示VM申请大小。
  • 对于静态区类型,该值始终为0,静态区使用的内存大小不在此处体现,而在于具体的VM Region中体现。
  • 对于其他VM类型,该大小为实际使用的物理内存大小。

RetainedSize

节点支配的所有节点的内存大小之和。

是否为Abandoned问题

当前节点是否为Abandoned Memory(如果不了解Abandoned定义,可以参考附录)。

是否为泄漏

当前节点是否为内存泄漏的节点。

常见的VM类型:

VM类型

说明

VM: Stack

栈内存,每个线程会有自己的线程栈以及TLS等数据,这些数据就保存在VM: Stack中,这个节点的孩子就是栈引用的临时变量以及TLS等数据,包括AutoreleasePool的入口也在此,可以根据大小判断该线程的类型,通常主线程为1008KB,GCD线程以及默认的线程为544KB,其他大小自行按业务映射。例如,TTPlayer的线程某些为160KB。

VM: ImageIO

ImageIO使用到的数据,通常是图片,使用imageNamed/imageWithData等方法常见此类型数据。

VM: CG Raster Data

CoreGraphics使用到的栅格数据,通常是图片,比如SDWebImage等常用框架都会使用CoreGraphics对图片进行绘制。在重度使用网络图片的App中,该类型VM通常是内存使用的主要类型。

WebKit Malloc

WebKit的堆内存,WebKit使用自己的bmalloc对内存进行管理而不使用libmalloc,跨端方案(小游戏、小程序、RN、Weex)和H5会大量使用该类型内存。

VM: IOSurface

跨进程、CPU与GPU共享数据的关键结构,通常是纹理、framebuffers、图片,常见于特效渲染、视频录制。

VM: IOKit

iOS驱动相关的内存分配,常见于纹理(比如Metal的一些纹理分配)存储等场景,和IOSurface一样,在特效渲染、视频录制等情况下会大量出现。

VM: LayerKit

CoreAnimation的内存使用,常见于Layer的BackingStore,如果图层过多,该类型内存会过高。

VM: SQLite Page Cache

SQLite分配的Page Cache。

VM: Type 0

多种可能,直接使用VM Alloc申请的即是该类型内存,Data段内存也属于该类内存。

引用树和支配树

图中左图为引用图,右图为其相应的支配树。
如果从根节点M到节点B必须经过节点A,则节点A支配节点B,离节点B最近的支配点,就是其在支配树上的父亲。
支配树中,如果父节点释放,那么父节点的所有子节点都被释放。
图片
引用树展示当前节点相关的引用关系,父节点是当前节点在引用树中的父节点,子节点是当前节点在引用树中的子节点。
支配树展示当前节点相关的支配关系,父节点是当前节点在支配树中的父节点,子节点是当前节点在支配树中的子节点。
通过引用树和支配树,可以快速定位节点最终被什么业务对象所引用,便于定位问题。
图片

  • 收起Heap父节点:压缩引用树中没有符号的Heap父亲节点。勾选后,将自动回溯,寻找当前节点到root节点的各条路径中第一个有符号意义的节点,然后将路径中的Heap节点隐藏,把这些有符号意义的节点展示在父节点列表中,可以帮助您快速找到有意义的业务对象。

  • 聚合展示:把相同类型(类+大小)的节点聚合在一起展示,并在括号中显示此类节点的数量。

  • 关系说明:引用树每一条节点都有一个表示引用关系的按钮,通过颜色表示不同的引用类型,显示文本为变量名或偏移,assign表示偏移0,Indirect表示间接引用关系。单击按钮可以切换节点,同时③基本信息和④引用树和支配树都将变为该节点对应的信息。
    关系说明:

    参数

    说明

    Indirect Relation

    非直接引用,当勾选收起Heap父节点后,会出现此类节点。

    Weak Ref

    弱引用。

    Strong Ref

    强引用。

    Unknown/Assign

    未知引用关系或assign。

  • 环:引用树里单击父节点进行回溯的过程中,某些节点可能会出现删除线,说明在回溯的过程中该节点曾经出现过环,可以在⑤回溯路径中查看生成环的路径中出现了哪些节点。
    图片

  • 节点信息:引用树和支配树的每一个节点的组成:类名+大小+(地址)+(支配大小,不包括自身内存) + 引用关系按钮。

回溯路径

回溯路径记录了从一个节点往祖先进行回溯时经过的路径,可以帮助您理清一个对象是如何被业务对象所引用的。
图片

  • 基节点信息:灰色字体,当前回溯路径的基节点,即路径开始的第一个节点。
  • 回退:基节点信息下方的蓝色字体,单击后可以回退到回溯路径上当前节点的上一节点。
  • 设置为根节点:单击后,可以把当前节点设置为回溯路径的根节点。
  • 路径搜索:单击后出现如下对话框,文本框中显示的是回溯路径,同时灰色字体说明当前的实例列表中有多少个节点具有此条回溯路径。单击确定,即可为实例列表中具有此条回溯路径的节点打上标签。
    图片
  • 路径列表:展示了当前回溯路径中出现的各个节点的信息,以及节点间的引用关系。

最佳实践

iOS单设备内存分析最佳实践

相关术语

术语

说明

Abandoned和泄漏

苹果官方文档介绍了iOS APP的内存分类:

  • Leaked memory:Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).
  • Abandoned memory:Memory still referenced by your application that has no useful purpose.
  • Cached memory:Memory still referenced by your application that might be used again for better performance.

APMPlus MemoryGraph采用类似GC的泄漏判定方法,如果从根节点出发无法到达节点A,则节点A泄漏,如果一个集合中的节点自己组成了一个独立的图,而且不与主图连通,则该集合为Abandoned。泄漏的判定不参考强弱引用关系,只参考可达性。

数据口径

MemoryGraph记录了APP的所有内存状态和关系:

  • Libmalloc Heap:由libmalloc分配的堆内存,业务和逻辑对象(OC/C\+\+/Swift/C结构体等)主要分配在该区域。
  • Virtual Memory:除Libmalloc Heap外的所有VM,包括图片、纹理、线程栈、Data段、非Libmalloc分配的堆内存等。
  • 引用关系:引用符号(OC/Swift对象支持成员变量名,其余的展示偏移)、OC/Swift额外记录强弱引用类型(性能原因暂不支持block捕获的强弱类型)。
  • 对于Libmalloc Heap中的内存,以Libmalloc中管理的内存块为单位作为单独的节点,对于VM以VM Region为单位作为单独的节点。

一般使用路径

MemoryGraph的使用方式并不是固定的,以下流程只是对于一些常见情况的惯用方法,可以根据自己的理解来灵活运用,让MemoryGraph成为帮助您解决内存问题的利器。

  1. 在聚类列表中查找突出和有问题的内存集合,通常同样大小的一类实例一般都有类似的用途,优先从占用内存大的集合出发。
    对于节点数特别多的异常场景,有时候也会从占用数量大的集合出发。
  2. 选择集合中的一个节点,查看其引用树父亲,一直回溯找到业务相关的对象。
  3. 如果最终的业务对象没有明显的直接强引用或者容器引用,考虑是否循环引用,排查block引用,借助代码逐个分析。
  4. 随机选择集合中的其他节点,看是否有类似的路径,确定问题。
  5. 如果在整个过程中发现某些对象异常,随机应变进行分析和处理。

查找所有的ViewController

  1. 找到一个ViewController实例,查看引用树。
  2. 在引用树中找到一个如下图的父节点。
    图片
  3. 查看这个HashTable的父节点,如果父节点是UIKitCore,则这个容器包含所有ViewController。
    图片
  4. 选择上图中的子节点,查看所有的ViewController。
    图片