【iOS】内存泄漏检查及原因分析

目录

    • 为什么要检测内存泄漏?
    • 什么是内存泄漏?
    • 内存泄漏排查方法
      • 1. 使用Zombie Objects
      • 2. 静态分析
      • 3. 动态分析方法
        • 定位修改
        • Leaks界面分析
        • Call Tree的四个选项:
    • 内存泄漏原因分析
      • 1. Leaked Memory:应用程序未引用的、不能再次使用或释放的内存。
      • 2. Abandoned Memory: 内存仍被应用程序所引用,没有任何有用的用途。
      • 3. Cached Memory:内存仍然由应用程序引用,可以再次使用以获得更好的性能。

前言

  • 内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。
  • 内存泄露( memory leak):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

为什么要检测内存泄漏?

迅速膨胀的内存可以很快让程序毙命,所以要多加防范。即使有 ARC(自动引用计数)内存管理机制,但在现实中对象之间引用复杂,循环引用导致的内存泄漏仍然难以避免,所以关键时刻还要自力更生。分析内存泄露不能把所有的内存泄露查出来,有的内存泄露是在运行时,用户操作时才产生的。

什么是内存泄漏?

内存泄漏指的是一块内存被分配后不再使用,但是没有被程序正确释放回系统,从而导致该内存继续占用在程序中,无法被其它任务使用。
这通常发生在使用了动态内存分配但未及时或正确释放,或者由于编程逻辑错误导致。如果不加以管理,会导致程序消耗过多的内存,甚至导致应用程序崩溃。

比如下MRC中如下代码会造成泄漏:

NSString* string = [[NSString alloc] init];
...
// [string release];  //ARC下,编译器自动添加此代码

但由于ARC机制,编译器会在适当的时机帮我们加上release代码,避免了内存泄漏。不过即使在ARC中也有肯能因对象不释放而引起内存泄漏,比如使用CF框架下的对象而没有做CFRelease操作。

虽然string所占的内存很小可以忽略不计,但也是有安全隐患的,就像前言所述,代码中这里泄漏一点内存,那里又泄漏一点内存,反反复复,内存总会有用尽的那一刻。
毕竟系统本身内存有限,分配给每个App的内存更加有限,当系统内存慢慢不足时,我们的App会变得越来越卡顿。
当系统内存告急时,App中首先会收到didRecieveWarning提醒,如果我们不第一时间采取措施释放内存,那么系统就会把我们的App Kill掉,所以我们应该重视内存泄漏问题。

didRecieveWarning调用流程看这篇文章:【iOS】didReceiveMemoryWarning实例方法

内存泄漏排查方法

1. 使用Zombie Objects

有时候我们会收到EXC_BASD_ACCESS错误提示,但没能跳到具体的出错代码行,此时可以启用Zombie Objects功能,来寻找那些已被释放的对象。

进入edit Scheme:
请添加图片描述

选中僵尸对象选项:
在这里插入图片描述

按照以上步骤开启Zombies Objects,而后Memory查看器变为disable:

请添加图片描述

系统在即将回收对象时,如果发现通过环境变量启用了僵尸对象功能,那么还将执行一个附加步骤,即把对象转化为僵尸对象,而不彻底回收。

测试代码:

void PrintClassInfo(id obj) {
    Class cls = object_getClass(obj);
    Class superCls = class_getSuperclass(cls);
    NSLog(@"=== %s : %s ===", class_getName(cls), class_getName(superCls));
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    UIView* view = [[UIView alloc] init];
    NSLog(@"Before release:");
    PrintClassInfo(view);
    
    [view release];
    
    NSLog(@"After release:");
    PrintClassInfo(view);
}

请添加图片描述

2. 静态分析

打开Xcode项目,并点击Product->Analyze:

在这里插入图片描述

静态内存泄漏分析如下:

在这里插入图片描述
请添加图片描述

静态分析方法能发现大部分的问题,但是只能是静态分析结果,有一些并不准确,还有一些动态分配内存的情形并没有进行分析。所以仅仅使用静态内存泄漏分析得到的结果并不是非常可靠,如果需要,我们需要将对项目进行更为完善的内存泄漏分析和排查。那就需要用到我们下面要介绍的动态内存泄漏分析方法Instruments中的Leaks方法进行排查。

3. 动态分析方法

打开Xcode项目,点击Product->Profile:

在这里插入图片描述

选择Leaks,这时项目也在模拟器或真机上运行起来了:

请添加图片描述

或者直接在自己的项目中运行程序,选中Memory点击右上角的Profile in instruments:

在这里插入图片描述

都可以进入下面的页面:

请添加图片描述

由于 Leaks 是动态监测,所以我们需要手动操作 APP,进行测试,一边操作 APP,一边观察 Leaks 的变化,在 暂停按钮 的右边 我们可以选择正在 运行的程序 & 选择设备 & App, 之后点击 红点 Record(红色圆圈按钮)运行。

请添加图片描述

观察,如果发现在 Leaks 里面有一个 红色X,这说明了我们的 APP 存在内存泄露。

就像这样

在这里插入图片描述

点击暂停,点击其中一个,然后我们开始分析。

定位修改

此时选中有红色叉的 Leaks,下面有个Leaks 字方格,点开,选中 Call Tree。

在这里插入图片描述

接着就是最关键的一步,在这个界面的右下角有若干选框,选中Invert Call Tree(从上到下跟踪堆栈信息) 和 Hide System Libraries(表示隐藏系统的函数)

在这里插入图片描述

在详情面板选中显示的若干条中的一条,双击,会自动跳到内存泄露代码处,然后点击右上角 Xcode 图标进行修改。

Leaks界面分析

Leaks 启动后会开始录制,随着对模拟器运行的 App 的操作,可以在 Leaks 中查看内存占用的情况。
Leaks顶部分为两栏:Allocations 和 Leaks,右侧的曲线代表内存分配和内存泄漏曲线。

Call Tree的四个选项:
  • Separate By Thread: 线程分离,只有这样才能在调用路径中能够清晰看到占用CPU最大的线程.每个线程应该分开考虑。只有这样你才能揪出那些大量占用CPU的"重"线程,按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程,它要处理和渲染所有的接口数据,一旦受到阻塞,程序必然卡顿或停止响应。
  • Invert Call Tree: 从上到下跟踪堆栈信息.这个选项可以快捷的看到方法调用路径最深方法占用CPU耗时(这意味着你看到的表中的方法,将已从第0帧开始取样,这通常你是想要的,只有这样你才能看到CPU中话费时间最深的方法),比如FuncA{FunB{FunC}},勾选后堆栈以C->B->A把调用层级最深的C显示最外面.反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。
  • 表示隐藏系统的函数,调用这个就更有用了,勾选后耗时调用路径只会显示app耗时的代码,性能分析普遍我们都比较关系自己代码的耗时而不是系统的.基本是必选项.注意有些代码耗时也会纳入系统层级,可以进行勾选前后前后对执行路径进行比对会非常有用.因为通常你只关心cpu花在自己代码上的时间不是系统上的,隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。隐藏缺失符号。如果 dSYM 文件或其他系统架构缺失,列表中会出现很多奇怪的十六进制的数值,用此选项把这些干扰元素屏蔽掉,让列表回归清爽。
  • 递归函数, 每个堆栈跟踪一个条目,拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。

内存泄漏原因分析

在目前主要以ARC进行内存管理的开发模式,导致内存泄漏的根本原因是代码总存在循环引用,从而导致一些内存无法释放,这就会导致dealloc方法无法被调用。

开启了ARC并不是就不会存在内存问题,苹果有句名言:ARC is only for NSObject

使用ARC的项目,一般内存泄漏都是 malloc、自定义结构、资源引起的,多注意这些地方进行分析。
注:如果你的项目使用了ARC,随着你的操作,不断开启或关闭视图,内存可能持续上升,但这不一定表示存在内存泄漏,ARC释放的时机是不固定的。

引起内存泄漏的几种原因:

1. Leaked Memory:应用程序未引用的、不能再次使用或释放的内存。

  • 在OC的ARC机制下,使用CF或CG对象时,忘记手动调用CFReleaseCGRelease

        CGImageRef* imageRef = CGImageCreateWithImageInRect(someImage, someRect);
    // ...
    //    CGImageRelease(imageRef); //  释放内存
    
  • for循环中超多次加载比较占内存的对象:频繁创建大量占用内存的对象,如果不使用@autorelease,会导致内存无法及时释放。

    for (int i = 0; i < 1000; ++i) {
       @autoreleasepool {
           UIImage* image = [UIImage imageNamed: @"largeImage"];
           // ...
       }
    }
    

2. Abandoned Memory: 内存仍被应用程序所引用,没有任何有用的用途。

大多是OC对象、block、timer、delegate等循环引用问题,造成引用计数一直不为零。

  • OC对象循环引用:

    @interface Person : NSObject
    @property (nonatomic, strong)Person* friends;
    @end
    
    @implementation Person
    - (void)dealloc {
        NSLog(@"Person 对象被释放");
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person* person1 = [[Person alloc] init];
            Person* person2 = [[Person alloc] init];
            person1.friends = person2;
            person2.friends = person1;
        }
        return 0;
    }
    

    person1和person2互相引用,形成了循环(强)引用,这两个对象的引用计数不会降为0,dealloc方法并没有被执行。
    解决办法就是使用弱引用打破循环:@property (nonatomic, weak)Person* friends
    - Block循环引用:

    @interface MyClass : NSObject
    @property (nonatomic, strong) void (^myBlock)(void);
    - (void)setupBlock;
    @end
    
    @implementation MyClass
    
    - (void)setupBlock {
        //  __weak typeof(self) weakSelf = self;
        self.myBlock = ^{
            //  __strong typeof(weakSelf) strongSelf = weakSelf;
            NSLog(@"%@", self); // 造成强引用循环
            //  if (strongSelf) {
            //      NSLog(@"%@", strongSelf);
            //  }
        };
    }
    
    @end
    
    MyClass *obj = [[MyClass alloc] init];
    [obj setupBlock];
    

    如果没有注释掉的那段代码,self.myBlockself产生了强引用,导致self的引用计数永远不会为零,从而引起循环引用。
    请添加图片描述

  • Timer循环引用:

    @interface MyClass : NSObject
    @property (nonatomic, strong) NSTimer *timer;
    @end
    
    @implementation MyClass
    - (void)startTimer {
        // __weak typeof(self) weakSelf = self;
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES];
    }
    
    - (void)timerFired {
        // Timer fired actions
    }
    @end
    

    如果没有注释掉的那段代码,NSTimerself保持强引用(target: self增加了ViewController的引用计数,如果不进行[timer invalidate];,就别想调用dealloc了),而selfNSTimer也保持强引用,形成循环引用。
    timer属性也最好设置为弱引用(weak)。

  • Delegate引起的循环引用:

    @interface MyObject : NSObject
    @property (nonatomic, strong)id<MyDelegate> delegate;
    @end
    
    @interface MyViewController : UIViewController <MyDelegate>
    @property (nonatomic, strong) MyObject *myObject;
    @end
    
    @implementation MyViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.myObject.delegate = self;
    }
    @end
    

    与Timer同理,myObjectdelegate使用强引用,delegate又对myObject保持强引用,形成循环引用。
    解决方案是使用弱引用:@property (nonatomic, weak)id<MyDelegate> delegate;

  • ViewController的子视图对self的持有
    我们有时候需要在子视图或者某个cell中点击跳转等操作,需要在子视图或cell中持有当前的ViewController对象,这样跳转之后的back键才能直接返回该页面,同时也不销毁当前ViewController。此时,你就要注意在子视图或者cell中对当前页面的持有对象不能是强引用,尽量assign或者weak,否则会造成循环引用,内存无法释放。

3. Cached Memory:内存仍然由应用程序引用,可以再次使用以获得更好的性能。

为了快速访问而存储起来的对象。

以缓存图片提高性能为例:

@class UIImage;
@interface ImageCache : NSObject
@property (nonatomic, strong) NSMutableDictionary* cache;
+ (instancetype)sharedInstance;
- (UIImage *)imageForKey:(NSString *)key;
- (void)setImage:(UIImage *)image forKey:(NSString *)key;
@end

//  ImageCache.m
#import "ImageCache.h"
#import "UIKit/UIImage.h"

@implementation ImageCache

+ (nonnull instancetype)sharedInstance {
    static ImageCache *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
        sharedInstance.cache = [NSMutableDictionary dictionary];
    });
    return sharedInstance;
}

- (void)setImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key {
    self.cache[key] = image;
}

- (nonnull UIImage *)imageForKey:(nonnull NSString *)key {
    return self.cache[key];
}

@end

// 使用缓存
UIImage* image = [UIImage imageNamed: @"example.png"];
[[ImageCache sharedInstance] setImage: image forKey: @"example"];

// 之后访问缓存的图片
UIImage* cachedImage = [[ImageCache sharedInstance] imageForKey: @"example"];
 ```
示例中,图片被缓存以便快速访问,从而提高性能。缓存图片使用的内存不是泄漏,因为这戏内存是有意保留以供将来使用的。


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java数据结构准备工作---常用类

文章目录 前言1.包装类1.1.包装类基本知识1.2.包装类的用途1.3.装箱和拆箱1.3.1.装箱&#xff1a;1.3.2.拆箱 1.4 包装类的缓存问题 2.时间处理类2.1.Date 时间类(java.util.Date)2.2.DateFormat 类和 SimpleDateFormat 类2.3.Calendar 日历类 3.其他常用类3.1.Math类3.2.Rando…

嵌入式中C语言经典的面试题分享

#error的作用是什么? #error 指令让预处理器发出一条错误信息,并且会中断编译过程。下面我们从Linux代码中抽取出来一小段代码并做修改得到示例代码: 这段示例代码很简单,当RX_BUF_IDX宏的值不为0~3时,在预处理阶段就会通过 #error 指令输出一条错误提示信息: "…

Python 很好用的爬虫框架:Scrapy:

了解Scrapy 爬虫框架的工作流程&#xff1a; 在scrapy中&#xff0c; 具体工作流程是这样的&#xff1a; 首先第一步 当爬虫引擎<engine>启动后&#xff0c; 引擎会到 spider 中获取 start_url<起始url> 然后将其封装为一个request对象&#xff0c; 交给调度器<…

文心一言 VS 讯飞星火 VS chatgpt (277)-- 算法导论20.3 4题

四、如果调用 vEB-TREE-INSERT 来插入一个已包含在 vEB 树中的元素&#xff0c;会出现什么情况&#xff1f;如果调用 vEB-TREE-DELETE 来删除一个不包含在 vEB 树中的元素&#xff0c;会出现什么情况&#xff1f;解释这些函数为什么有相应的运行状况&#xff1f;怎样修改 vEB 树…

【手推公式】如何求SDE的解(附录B)

【手推公式】如何求SDE的解&#xff08;附录B&#xff09; 核心思路&#xff1a;不直接求VE和VP的SDE的解xt&#xff0c;而是求xt的期望和方差&#xff0c;从而写出x0到xt的条件分布形式&#xff08;附录B&#xff09; 论文&#xff1a;Score-Based Generative Modeling throug…

【SpringBoot + Vue 尚庭公寓实战】根据类型查询标签列表接口实现(五)

【SpringBoot Vue 尚庭公寓实战】根据类型查询标签列表接口实现&#xff08;五&#xff09; 文章目录 【SpringBoot Vue 尚庭公寓实战】根据类型查询标签列表接口实现&#xff08;五&#xff09;1、查看接口2、进行开发 1、查看接口 启动项目 访问&#xff1a;http://localho…

快排(快速排序)的递归与非递归实现(文末附完整代码)

快排有几种不同的写法&#xff0c;下面一一来介绍并实现。其中又分为递归和非递归的写法&#xff0c;但大体思路相同&#xff0c;只是代码实现略有不同。(注&#xff1a;文章中的完整代码中&#xff0c;Swap()函数均省略未写&#xff0c;记得自己补充) 递归写法 递归的写法类…

手写kNN算法的实现-用欧几里德空间来度量距离

kNN的算法思路&#xff1a;找K个离预测点最近的点&#xff0c;然后让它们进行投票决定预测点的类型。 step 1: kNN存储样本点的特征数据和标签数据step 2: 计算预测点到所有样本点的距离&#xff0c;关于这个距离&#xff0c;我们用欧几里德距离来度量&#xff08;其实还有很多…

LangChain4j实战

基础 LangChain4j模型适配: Provider Native Image Sync Completion Streaming Completion Embedding Image Generation Scoring Function Calling OpenAI ✅ ✅ ✅ ✅ ✅ ✅ Azure OpenAI ✅ ✅ ✅ ✅ ✅ Hugging Face ✅ ✅ Amazon Bedrock ✅ ✅…

UltraScale+系列模块化仪器,可以同时用作控制器、算法加速器和高速数字信号处理器

基于 XCZU7EG / XCZU4EG / XCZU2EG • 灵活的模块组合 • 易于嵌入的紧凑型外观结构 • 高性能的 ARM Cortex 处理器 • 成熟的 FPGA 可编程逻辑 &#xff0c;基于 IP 核的软件库 基于 Xilinx Zynq UltraScaleMPSoC 的 FPGA 技术&#xff0c;采用 Xilinx Zynq UltraScale&a…

陆面生态水文模拟与多源遥感数据同化技术

原文链接&#xff1a;陆面生态水文模拟与多源遥感数据同化技术 了解陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用;熟悉模 型的发展历程&#xff0c;常见模型及各自特点;理解Noah-MP模型的原理&#xff0c;掌握Noah-MP 模型在单 站和区域的模拟、模拟结果的…

华为坤灵路由器配置SSH

配置SSH服务器的管理网口IP地址。 <HUAWEI> system-view [HUAWEI] sysname SSH Server [SSH Server] interface meth 0/0/0 [SSH Server-MEth0/0/0] ip address 10.248.103.194 255.255.255.0 [SSH Server-MEth0/0/0] quit 在SSH服务器端生成本地密钥对。 [SSH Server…

C语言:定义和使用结构体变量

定义和使用结构体变量 介绍基础用法1.定义结构体2. 声明结构体变量3. 初始化和访问结构体成员4. 使用指针访问结构体成员5. 使用结构体数组 高级用法6. 嵌套结构体7. 匿名结构体8. 结构体和动态内存分配9. 结构体作为函数参数按值传递按引用传递 介绍 在C语言中&#xff0c;结…

统信UOS1070上配置文件管理器默认属性01

原文链接&#xff1a;统信UOS 1070上配置文件管理器默认属性01 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在统信UOS 1070上配置文件管理器默认属性的文章。文件管理器是我们日常操作系统使用中非常重要的工具&#xff0c;了解如何配置其默认属性可以极大地…

最新下载:PDFFactoryFinePrint【软件附加安装教程】

简介&#xff1a; pdfFactory是一款无须 Acrobat 创建 Adobe pdf 文件的打印机驱动程序&#xff0c; 提供的创建 PDF 文件的方法比其他方法更方便和高效。 pdfFactory 支持从所有应用程序轻松、可靠地创建 PDF 文件。 支持将单页或两页的文档&#xff0c;直接打印为PDF文件&a…

python 多任务之多线程

多线程 线程是程序执行的最小单位&#xff0c;实际上进程只负责分配资源&#xff0c;而利用这些资源执行程序的是线程&#xff0c;也就是说进程是线程的容器&#xff0c;一个进程中最少有一个线程来负责执行程序&#xff0c;它可以与同属一个进程的其它线程共享进程所拥有的全…

基于Simulink的双端行波测距

1 输电线路故障仿真模型 基于双端行波测距理论&#xff0c;在MATLAB软件中搭建的三相50Hz的输电线路故障仿真模型如图1所示&#xff0c;该模型包含了三相电源、输电线路、故障发生器和示波器模块等。主要仿真参数设置如下:仿真时间为 0~0.1s,采用固定步长 10-7和ode3 算法&…

为什么Kubernetes(K8S)弃用Docker:深度解析与未来展望

为什么Kubernetes弃用Docker&#xff1a;深度解析与未来展望 &#x1f680; 为什么Kubernetes弃用Docker&#xff1a;深度解析与未来展望摘要引言正文内容&#xff08;详细介绍&#xff09;什么是 Kubernetes&#xff1f;什么是 Docker&#xff1f;Kubernetes 和 Docker 的关系…

小柴带你学AutoSar系列一、基础知识篇(5)makefile基础

Flechazohttps://www.zhihu.com/people/jiu_sheng 小柴带你学AutoSar总目录https://blog.csdn.net/qianshang52013/article/details/138140235?spm=1001.2014.3001.5501

动态IP在云计算中的应用与优势(短效IP的作用)

一、云计算概述 云计算是指通过互联网将计算资源和服务提供给用户的一种模式。它具有高灵活性、可扩展性和成本效益等特点&#xff0c;使得企业能够快速响应市场变化&#xff0c;降低IT投入成本。云计算的核心优势在于其资源的动态分配和高效利用。 二、动态IP在云计算中的角…