iOS_Memory Leak 内存泄露治理

news2024/12/23 11:56:30

1、内存分类

官方文档介绍 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

  • Leaked memory:app 没有引用的内存,无法再次使用或释放(可以使用 Leaks 工具检测)
  • Abandoned memory:app 仍有引用,但没有任何用途的内存
  • Cached memory:app 仍有引用,可能会再次使用以获得更好的性能

Leaked memoryAbandoned memory 都是应该释放而没释放的内存,属于内存泄露。

Leaked memory 可以用 InstrumentLeaks 检测出来。Leaks的实现思路是搜索所有可能包含指向 malloc 内存块指针的内存区域,比如全局数据内存块,寄存器和所有的栈。如果 malloc 内存块的地址被直接或者间接引用,则是 reachable 的,反之则是 leaks

Abandoned memory可以用 InstrumentAllocations 检测出来。检测方法是用 Mark Generation 的方式,当每次点击 Mark Generation 时,Allocations 会生成当前 App 的内存快照,而且 Allocations 会记录从上回内存快照到这次内存快照这个时间段内,新分配的内存信息.


2、Memory Report

Xcode 运行项目时,切换到 Debug navigator 点击 memory 就可以查看 Memory Report,显示内存使用的整体情况:
图片名称

用于定位内存泄露的话用处不大,只能看到内存的概况。


3、Analyze

静态分析入口:
图片名称

分析案例:在这里插入图片描述
缺陷:只能检查编译时的内存泄漏,并不能检测到所有的内存泄漏,主要是因为有些泄漏是发生在运行时,或需要用户操作才会产生。


4、Leaks

4.1、前置设置

首先,修改编译设置生成符号信息,以便 Leaks 分析出调用堆栈函数符号:
Target -> Build Settings -> Build Options -> Debug Information Format -> Debug -> DWAPR with dSYM File

否则 Leaks 无法解析调用堆栈函数名:
no stack trace is available for this leak; it may have been allocated before the Allocation instrument was attached

将 app 通过 Xcode 装到手机上后,入口在菜单栏:Xcode -> Open Developer Tool -> Instruments -> 然后选择 Leaks -> Choose (打开操作面板)
图片名称
图片名称

4.2、页面介绍

步骤1:选好设备和需要测试的 app
步骤2:点击同行最左边的红色按钮,开始录制(点击开始录制会重启 app)
图片名称

录制过程中,左边按钮是停止,右边按钮是暂停:
图片名称

Leaks 录制过程中会出现3种标志:

  • 绿色:没有发现泄露
  • 红色:发现新的泄露
  • 灰色:没有发现新的泄露
    图片名称

4.3、使用

4.3.1、Leaks

下半部分显示的是泄露的详情,左边是目前为止检测到的所有泄露;选中其中一个,右侧显示的是泄露点的调用堆栈,可据此找到泄露点进行修改。
图片名称

底部栏:

  • snapshots,可以设置检测泄露的时间间隔,也有立即检测按钮:
    图片名称

  • Input Filter可通过线程过滤

  • Detail Filter可通过关键字过滤
    图片名称

也可选择时间段过滤:在起始时间点按下鼠标左键,拖动到截止时间点松开:
图片名称

4.3.2、Cycles & Roots:

点击中间栏的左侧切换到Cycles & Roots模式,可查看泄露图:
图片名称

看图分析应该是因为block导致的循环引用,按调用堆栈找到对应的代码:
图片名称

4.3.3、Call Tree:

点击中间栏的左侧切换到Call Tree统计模式,也可通过底部栏的工具进行过滤
Separate By Thread:线程分离,在调用路径中能够清晰看到占用内存最大的线程
Invert Call Tree:反转调用堆栈顺序
Hide System Libraries:隐藏系统库的调用堆栈信息
Flatten Recursion:会将调用栈里递归函数作为一个入口(很少使用)
在这里插入图片描述

底部栏可设置各种约束进行过滤(用的比较少):
按符号过滤 or 按库过滤
图片名称

设置最大最小值进行过滤:
图片名称

设置 符号/库 变化时/删减掉 进行过滤:
图片名称


5、Memory Graph

5.1、介绍

1)修改配置:
图片名称

Malloc Scribble:开启将使用预定义的值填充释放的内存,从而在内存泄漏时更加明显。这提高了Xcode识别泄漏的准确性。
Malloc Stack Logging:启用此选项将允许Xcode构建分配回溯,以帮助了解对象从何处引用。

Xcode 运行项目时可点击中部栏的Debug Memory Graph按钮,查看内存图:
图片名称

2)入口:
再点击左侧 导航栏 - 底部栏 的 Show only leaked allocations 按钮,可过滤出泄露的对象:
图片名称

或者在底部Filter栏输入前缀过滤出当前还存在的对象进行分析:
图片名称

3)使用graph分析:

5.2、使用方式1:

直接通过 Show only leaked allocations 过滤出明显的泄露进行修复

例如:动画用到的 CGPath 没有释放:
图片名称

5.3、使用方式2:

退出页面后点击 Debug Memory Graph,分析没有释放的对象,是否为内存泄露

例如:退出直播间应该释放的插件没有释放:
图片名称

以上介绍的都是 Xcode 自带的可视化工具,下面介绍的是其他代码检测工具。


8、FBRetainCycleDetector

Facebook 的开源循环引用检测工具 FBRetainCycleDetector
当确认或怀疑一个对象是否泄露时,都可以使用该工具查找循环引用链,在合适的位置加入查找逻辑: (如:VC的didAppear 或 其他对象确认已经投入使用时)

#import <FBRetainCycleDetector/FBRetainCycleDetector.h>

FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"retain cycle: %@ %@", [self class], retainCycles);

输出,例如:

(
    "-> MyTableViewCell ",
    "-> _callback -> __NSMallocBlock__ "
)

表示:cell 持有 block,block 持有 cell


8、RaftKit

腾讯视频已集成的 RaftKit (未开源)里的有 内存泄露监控 工具(底层用的是Bugly):
图片名称

打开开关和提示弹框:
图片名称

打开后,当发现泄露会弹出alert:
图片名称

打开 RaftKit 在内存泄露工具里,查看内存泄露记录文件:
图片名称

点击需要分析的泄露对象,查看详情:
图片名称

内部也是使用FBRetainCycleDetector进行引用循环链的查找:
图片名称

也可将文件导出:FloatingWebVC.txt
分析详情中的循环引用链:左边是实例名,右边实例的类型;从第一个到最后一个形成了一个引用环。
找到对应的类进行分析:
图片名称

QNBUALiveShowLayoutBridgeBase 是持有 jsBridge 的,且 jsBridge 又间接持有该 block,所以在 block 里直接使用 self 就形成了引用环了。
(26个Handler,95% block 的写法都导致了循环引用)


9、MLeaksFinder

Tencent 的开源检测内存泄露库:MLeaksFinder
可在日常开放中默认打开,以便及时获得泄露警告,而不用特意打开以上工具去排查。

9.1、使用:

podfile里添加导入,然后执行 pod install

pod 'MLeaksFinder'
pod 'FBRetainCycleDetector'

使用 MLeaksFinder.h 的宏 MEMORY_LEAKS_FINDER_ENABLED 控制该工具是否可用.

MLeaksFinder 发现内存泄露时会弹出 Memory Leak 的 alert :

Memory Leak
(
    MyTableViewController,
    UITableView,
    UITableViewWrapperView,
    MyTableViewCell
)

表示:MyTableViewController,UITableView,UITableViewWrapperView 都已成功释放,但其 subView MyTableViewCell 没有释放。

并会持续追踪该对象的生命周期,并在该对象释放时给出 Object Deallocated 的 alert :

Object Deallocated
(
    MyTableViewController,
    UITableView,
    UITableViewWrapperView,
    MyTableViewCell
)

9.2、分析 alert:

情形1、单例 or 被 cache 起来的对象

如下所示,在第一次 pop 时报了 Memory Leak,在之后重复 push 并 pop 同一个 ViewController 过程中,即不报 Object Deallocted,也不报 Memory Leak。这种情况可以确定该对象是被设计成单例 or 被 cache 起来了。

    pop             push           pop           push          pop
----------> Leak ----------> | ----------> | ----------> | ---------->

情形2、释放不及时

如下所示,在第一次 pop 时报 Memory Leak,在之后的重复 push 和 pop 同一个 ViewController 过程中,对于同一个类不断地报 Object DeallocatedMemory Leak。这种情况属于释放不及时。

    pop             push                 pop             push                 pop
----------> Leak ----------> Dealloc ----------> Leak ----------> Dealloc ----------> Leak

情形3、真正的泄露

如下所示,在第一次 pop 时报 Memory Leak,在之后的重复 push 和 pop 同一个 ViewController 过程中,不报 Object Deallocated,但每次 pop 之后又报 Memory Leak。这种每次进入并退出一个页面后都报内存泄露,且被报泄露对象又从来没有释放过,可以确定是真正的内存泄露。

    pop             push           pop             push           pop
----------> Leak ----------> | ----------> Leak ----------> | ----------> Leak

9.3、查找循环引用链:

MLeaksFinder里也用了FBRetainCycleDetector来找找循环引用链:
MEMORY_LEAKS_FINDER_ENABLED控制是否启用FBRetainCycleDetector查找循环引用链;
_INTERNAL_MLF_RC_ENABLED设置alert弹框是否显示Retain Cycle按钮;

9.4、扩展:

MLeaksFinder 目前只检测 ViewController 跟 View 对象。为此,MLeaksFinder 提供了一个手动扩展的机制,开发者可以从 UIViewController 跟 UIView 出发,去检测其它类型的对象的内存泄露。如下所示,可以检测 UIViewController 持有的 View Model:

- (BOOL)willDealloc {
    if (![super willDealloc]) {
        return NO;
    }
    MLCheck(self.viewModel);
    return YES;
}

9.5、原理

NSObject新增一个-willDealloc方法:在 2s 后给弱引用的self发送assertNotDealloc消息:
self被释放则不会执行;
self未被释放则会执行assertNotDeall
图片名称

然后在UIViewControllerdismiss方法里调用willDealloc:遍历 childVCspresentVCssubViews触发他们的willDealloc方法检测是否有泄露:
图片名称


10、泄露总结:

通过排查腾讯视频直播间的整体泄露后,发现泄露类型基本都是以下5类:

10.1、Block

图片名称

10.2、NSTimer

NSTimer 为什么这么容易导致内存泄露:
很重要的一点是因为 RunLoop 会强引用 NSTimer(系统实现的无法做修改)。
所以开发者必须在恰当的时机将NSTimer释放掉。
而一般最佳释放时机为持有 NSTimerself dealloc 方法里:

- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
}

iOS10之前的方法,需要传入target(一般我们用self)作为代理,执行需要定时触发的方法。
因为NSTimer会强引用传入的target(这也是系统实现的无法修改)。
当开发者直接传入 self 时,就导致了 self 无法被释放,进而在 dealloc 里释放 NSTimer 的代码也不会执行,从而导致了内存泄露:RunLoop -> NSTimer -> self (不是引用环,但是无法释放)

iOS10苹果新出了3个方法,采用block的形式实现代理方法,不需要传入self(block中还是需要用weakSelf),从而保证了selfdealloc的执行。

更多计时器介绍可见:iOS_定时器:NSTimer、GCDTimer、DisplayLink

10.3、malloc -> free

malloc 申请的内存没有使用 free 释放,用 Leaks 检测比较方便:
图片名称

10.4、CFBridgingRetain - CFBridgingRelease

调用了 CFBridgingRetain 进行 +1 持有后,没有调用 CFBridgingRelease 进行 -1 的:
在这里插入图片描述

10.5、单例滥用

一个点赞动效使用了单例,退出直播间没有释放:
图片名称


11、工具总结:

Memory Report:只能看到内存使用的整体情况,用处不大
Analyze:只能检查编译时期的内存泄漏,不能检测运行时产生的泄露
Leaks:适合发现持续的泄露
Memory Graph:适合发现退出后没有释放的内存泄露
FBRetainCycleDetector:用于查找循环引用链,搭配其他查找泄露对象工具使用
MLeaksFinder:可查找VC和View的泄露,代码开源也可进行DIY拓展


参考:

iOS内存泄漏检查&原理
iOS内存分析原理
检测和诊断 App 内存问题
MLeaksFinder
MLeaksFinder 新特性
MLeaksFinder:精准 iOS 内存泄露检测工具
MLeaksFinder 原理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/189382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式 - 结构型模式_桥接模式

文章目录结构型模式概述CaseBad ImplBetter Impl小结结构型模式 结构型模式主要是解决如何将对象和类组装成较大的结构&#xff0c; 并同时保持结构的灵活和⾼效。 结构型模式包括&#xff1a;适配器、桥接、组合、装饰器、外观、享元、代理&#xff0c;这7类 概述 桥接模式的…

2023牛客寒假算法基础集训营4

A-清楚姐姐学信息论 链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 不同进制对于信息的表示效率不同&#xff0c;清楚姐姐最近学习了信息论中使用不同进制表示信息的方法&#xff0c;她现在想要比较两种不同进制表示信息时&#xff0c;谁的…

从软件角度看PCIe设备的硬件结构

从软件角度看PCIe设备的硬件结构 文章目录从软件角度看PCIe设备的硬件结构参考资料&#xff1a;一、 PCIe接口引脚二、 从软件角度理解硬件接口2.1 PCI/PCIe地址空间转换2.2 PCIe上怎么传输地址、数据三、 PCIe系统的硬件框图致谢参考资料&#xff1a; 《PCI Express Technolo…

ElasticSearch概念与架构原理

文章目录一、概述二、ElasticSearch架构原理三、ElasticSearch搜索入门一、概述 ElasticSearch简介 简介 ES是建立在Lucene基础之上的分布式准实时搜索引擎&#xff0c;它所提供的诸多功能中有一大优点&#xff0c;就是实时性好。比如&#xff1a;在业务需求中&#xff0c;新增…

计算机图形学 第7章 自由曲线曲面

先说好&#xff0c;第八章不学。 目录学习目标曲线与曲面的表示形式插值与逼近Bezier曲线定义一次Bezier曲线二次Bezier曲线⭐⭐⭐三次Bezier曲线⭐⭐⭐三次Bezier曲线的Bernstein基函数&#xff1a;Bernstein基函数的性质Bezier曲线的性质de Casteljau算法几何作图法绘制Bezie…

Struts2之拦截器

Struts2之拦截器1、Struts2体系架构1.1、执行流程1.2、核心接口和类1.3、流程简图2、Struts2拦截器2.1、使用拦截器的目的2.2、拦截器的简介2.3、拦截器的工作原理2.4、拦截器的使用2.4.1、创建自定义拦截器2.4.2、struts.xml中定义和配置拦截器2.4.3、Struts2默认拦截器2.4.4、…

Leetcode.2319 判断矩阵是否是一个 X 矩阵

题目链接 Leetcode.2319 判断矩阵是否是一个 X 矩阵 Rating : 1201 题目描述 如果一个正方形矩阵满足下述 全部 条件&#xff0c;则称之为一个 X矩阵 &#xff1a; 矩阵对角线上的所有元素都 不是 0 矩阵中所有其他元素都是 0 给你一个大小为 n x n的二维整数数组 grid&#…

ElasticSearch - 旅游酒店案例es功能实现

目录 案例 搜索与分页功能 条件过滤功能 附近的酒店功能 广告置顶功能 HotelService(es操作)总览 案例 搜索与分页功能 案例需求&#xff1a;实现旅游的酒店搜索功能&#xff0c;完成关键字搜索和分页实现步骤如下&#xff1a;1.定义实体类&#xff0c;接收前端请求实体…

微信小程序用vant自定义tabbar页面并跳转相应页面

0.前置安装 步骤一 安装 vant 组件库 npm i vant/weapp -S --production下载完后要npm构建才能使用 步骤二 修改 app.json 将 app.json 中的 "style": "v2" 去除&#xff0c;小程序的新版基础组件强行加上了许多样式&#xff0c;难以覆盖&#xff0c;不…

分布式定时任务框架选型

目录 1. 前言 2. 定时任务框架 3. 分布式任务调度系统对比 4. 和quartz框架对比 5. 综合对比 6. 总结和结论 7. 附定时任务的其他方案 1. 前言 我们先思考下面几个业务场景的解决方案: 支付系统每天凌晨1点跑批&#xff0c;进行一天清算&#xff0c;每月1号进行上个月清…

【算法】快速排序算法原理及实现

1.什么是快速排序算法 快速排序是对冒泡排序的一种改良版&#xff0c;通过一趟排序&#xff0c;把要排序的序列分割成两个部分&#xff0c;一部分的所有数据要比另一部分的数据都小&#xff0c;然后再根据这两部分的数据来进行快速排序。以此来达到整一个数据都变成了有序序列…

AI算法创新赛-人车目标检测竞赛总结01

AI0000020摘要:人车目标检测竞赛主要考察目标检测算法与 TPU 部署推理&#xff0c;主要考察算法选型与调 优&#xff0c;面向算能 TPU 迁移部署与推理加速两项能力;主要考核目标是算法效果(mAP)与推 理性能(单张图片推理时间)。针对这些要求&#xff0c;笔者从算法选型&#xf…

【实际开发11】- 统计 / 科学计算 - 1

目录 1. sql 统计返回值为 null 时 , 赋值 为 : 0 ( return UI ) 1. 手动 null 判断 , 进行 “0” 赋值 2. XxxxVO 展示对象 , 初始化时 , 赋值默认值 ( 待优化 ) 2. 统计异常 1. 注意中间表数据的维护 ( 同步删除 / 避免手动删数据 ) 3. 精度损失 1. Java 类型 float、…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:LiveStream和LiveStreamPlayer

本文简述如何在Smobiler中使用LiveStream和LiveStreamPlayer。 LiveStream 直播推送插件 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入LiveStream和Button&#xff0c;布局如下 选中LisvStream&#xff0c;在设计器中设置Url&#xff08;需要事先准备一个视频…

【面试原型链】前端面试那些事(1)之原型链详解

【写在前面】辞旧迎新的春季佳节&#xff0c;在这里博主先祝各位看官新的一年赚钱多多&#xff0c;知识满满&#xff0c;年后谈到面试&#xff0c; 好多人在面试的时候就倒在原型链知识点上面&#xff0c;主要原因还是基本功不够扎实&#xff0c;针对以前的知识我也计划在2023年…

C语言小题,通过指向结构体变量的指针变量输出结构体变量中成员的信息。(指针结构体变量的指针)

前言&#xff1a; 此篇是针对 指针结构体变量的指针 方面的练习。 解题思路&#xff1a; 在已有的基础上&#xff0c;本题要解决两个问题&#xff1a; &#xff08;1&#xff09;怎样对结构体变量成员赋值&#xff1b; &#xff08;2&#xff09;怎样通过指向结构体变量的指针…

天云数据:Hubble数据库系统自主研发率99.62%,是真正的信创数据库

软件是新一代信息技术的灵魂&#xff0c;是数字经济发展的基础&#xff0c;是制造强国、网络强国、数字中国建设的关键支撑。2021年&#xff0c;工信部印发的《“十四五”软件和信息技术服务业发展规划》明确指出&#xff0c;要聚力攻坚基础软件&#xff0c;关键基础软件补短板…

Javascript基础复盘5

内置对象 值属性 这些全局属性返回一个简单值&#xff0c;这些值没有自己的属性和方法。 InfinityNaNundefinedglobalThis函数属性 全局函数可以直接调用&#xff0c;不需要在调用时指定所属对象&#xff0c;执行结束后会将结果直接返回给调用者。 eval()uneval()isFinite()isN…

Q-Learning以及.NET环境下的实现

Q-Learning以及.NET环境下的实现写在前面机器学习Q-Learning环境准备试题准备解题过程写在结尾写在前面 看过我的文章的朋友应该都知道&#xff0c;我之前一直在研究视觉相关的知识&#xff0c;也看了一些卷积神经网络&#xff08;CNN&#xff09;&#xff0c;深度学习相关的文…

强化学习之:价值学习Value-Based Learning

文章目录参考内容动作价值函数&#xff08;Action-value Function&#xff09;深度强化学习&#xff08;DQN&#xff09;学习目标&#xff08;Goal&#xff09;如何获得尽可能好的 Q∗(st,a)→Q^{*}(s_t, a) \rightarrowQ∗(st​,a)→ 用神经网络通过学习获得时间差分算法&…