【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 对象被释放");
    }
    @endint 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);//  }};
    }@endMyClass *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"];```
示例中,图片被缓存以便快速访问,从而提高性能。缓存图片使用的内存不是泄漏,因为这戏内存是有意保留以供将来使用的。

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

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

相关文章

我的编程语言学习记录:一段不断探索的旅程

目录 我的编程语言学习记录&#xff1a;一段不断探索的旅程 1.引言 2.我的编程之旅开始 第一站&#xff1a;Python — 简洁之美 第二站&#xff1a;JavaScript — 网页的魔法 第三站&#xff1a;Java — 企业级的力量 3.学习过程中的挑战与克服 1.理解概念 3.记忆语法…

牛客网刷题 | BC118 N个数之和

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 输入数字N&#xf…

Servlet-01

文章目录 Servlet创建Servlet探究Servlet的生命周期 HttpServletWebServlet注解详解 重定向与请求转发ServletContextServletContext中的接口 HttpServletRequestHttpServletResponse状态码解释Cookie Servlet Q&#xff1a;它能做什么呢&#xff1f; A&#xff1a;我们可以通…

Hadoop3:MapReduce源码解读之Map阶段的数据输入过程整体概览(0)

一、MapReduce中数据流向 二、MapTask并行度 1、原理概览 数据块&#xff1a;Block是HDFS物理上把数据分成一块一块。数据块是HDFS存储数据单位。 数据切片&#xff1a;数据切片只是在逻辑上对输入进行分片&#xff0c;并不会在磁盘上将其切分成片进行存储。数据切片是MapRed…

XUbuntu24.04之ch9344(usb转串口芯片)安装驱动(二百四十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

xshell远程无法链接上VM的centos7

1、现象如下&#xff0c; 2.解决办法&#xff1a;查证后发现这个默认的设置为vmnet0 3.参考文章&#xff1a;Xshell连接不上虚拟机centos7_centos7的nat模式可以ping通网络,但是用xshell连不上是什么原因-CSDN博客

从GAN到WGAN(01/2)

从GAN到WGAN 文章目录 一、说明二、Kullback-Leibler 和 Jensen-Shannon 背离三、生成对抗网络 &#xff08;GAN&#xff09;四、D 的最优值是多少&#xff1f;五、什么是全局最优&#xff1f;六、损失函数代表什么&#xff1f;七、GAN中的问题 一、说明 生成对抗网络 &#…

Camtasia Studio怎么自动加字幕呢,Camtasia Studio有什么功能呢

在信息化高度发达的今天&#xff0c;视频作为一种直观、生动的信息表达方式&#xff0c;受到了越来越多人的青睐。无论是教育领域的教学视频&#xff0c;还是企业宣传的推广短片&#xff0c;甚至是个人创作的分享作品&#xff0c;都离不开一款优秀的视频编辑软件。Camtasia Stu…

【Python数据分析--pandas学习笔记】Python数据分析库pandas详细学习笔记(内容详细,适合小白入门),数据分析学习笔记

一&#xff0c;pandas教程 1-1 pandas 安装 1-1-1 使用 pip 安装 pandas: pip install pandas安装成功后&#xff0c;我们就可以导入 pandas 包使用&#xff1a; import pandas1-1-2 查看 pandas 版本 >>> import pandas >>> pandas.__version__ # 查看…

数据+AI 打造企业的“金山银山”

今日之世界&#xff0c;数据是生产资料&#xff0c;而人工智能&#xff08;AI&#xff09;是生产工具&#xff0c;它们的结合&#xff0c;带来的是业务的增长、新质生产力的提升&#xff0c;就是金山银山。 创新是源动力 凡是到过浙江省安吉县余村的人&#xff0c;应该都会被它…

【云原生_K8S系列】Kubernetes 控制器之 Deployment

在 Kubernetes 中&#xff0c;Deployment 是一种高级控制器&#xff0c;负责管理应用的部署和生命周期。它提供了一种声明性的方式来定义应用的期望状态&#xff0c;并确保实际状态与期望状态保持一致。Deployment 可以自动处理应用的滚动更新、扩展和回滚等任务&#xff0c;是…

Recognize Anything: A Strong Image Tagging Model(RAM模型使用方法)

一、RAM模型介绍 这篇论文介绍了一个名为“Recognize Anything Model”&#xff08;RAM&#xff09;的新型基础模型&#xff0c;专用于图像标签识别&#xff08;图像分类&#xff09;。这一模型采用大规模图像-文本配对数据进行训练&#xff0c;无需手动注释&#xff0c;能够在…

关于焊点检测(SJ-BIST)模块实现

关于焊点检测&#xff08;SJ-BIST&#xff09;模块实现 语言 &#xff1a;Verilg HDL 、VHDL EDA工具&#xff1a;ISE、Vivado、Quartus II 关于焊点检测&#xff08;SJ-BIST&#xff09;模块实现一、引言二、焊点检测功能的实现方法&#xff08;1&#xff09; 输入接口&#x…

数据库(28)——联合查询

对于union查询&#xff0c;就是把多次查询的结果合并起来&#xff0c;形成一个新的查询结果集。 语法 SELECT 字段列表 FROM 表A... UNION [ALL] SELECT 字段列表 FROM 表B...; 演示 select * from user where age > 22 union all select * from user where age < 50; u…

【Python机器学习】PCA——特征提取(2)

上一篇写过了用单一最近邻分类器训练后的精度只有0.22. 现在用PCA。想要度量人脸的相似度&#xff0c;计算原始像素空间中的距离是一种相当糟糕的方法。用像素表示来比较两张图像时&#xff0c;我们比较的是每个像素的灰度值与另一张图像对应位置的像素灰度值。这种表示与人们…

iOS Universal Links 配置

前言 Universal Links 通用链接&#xff0c;我的理解就是通过点击这个链接&#xff0c;能够打开应用&#xff0c;还可以根据配置的路径和参数跳转到App内指定页面和传递参数。我们做微信QQ登录分享时会要求配置这个链接。网上有很多文章介绍了如何配置&#xff0c;但是每次随便…

力扣经典面试题-旋转链表(Java)

1.题目描述&#xff1a;给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3] 示例 2&#xff1a; 输入&#xff1a;head [0,1,2], k …

python数据分析-连云港石化基地2023年用电量分析

接下来对连云港石化基地2023年用电量进行分析&#xff0c;首先导入数据分析基本的包&#xff1a; import pandas as pd import matplotlib.pyplot as plt# Load the data from the provided Excel files file_path1 data1.xlsx file_path2 data2.xlsxdata1 pd.read_excel(f…

设计模式之观察者模式ObserverPattern(十一)

一、概述 观察者模式 (Observer Pattern) 是一种行为型设计模式&#xff0c;又被称为发布-订阅 (Publish/Subscribe) 模式&#xff0c;它定义了对象之间的一种一对多的依赖关系&#xff0c;使得当一个对象的状态发生变化时&#xff0c;所有依赖于它的对象都会自动收到通知并更新…

Leetcode 力扣109. 有序链表转换二叉搜索树 (抖音号:708231408)

给定一个单链表的头节点 head &#xff0c;其中的元素 按升序排序 &#xff0c;将其转换为 平衡 二叉搜索树。 示例 1: 输入: head [-10,-3,0,5,9] 输出: [0,-3,9,-10,null,5] 解释: 一个可能的答案是[0&#xff0c;-3,9&#xff0c;-10,null,5]&#xff0c;它表示所示的高…