iOS - 内存管理

这篇具有很好参考价值的文章主要介绍了iOS - 内存管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、App 内存分布

iOS - 内存管理

二、OC对象的内存管理

iOS 中,使用引用计数来管理 OC 对象的内存,新创建的 OC 对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。调用 retain 会让 OC 对象的引用计数 +1,调用 release 会让 OC 对象的引用计数-1。

// 引用计数散列表(在64bit中,引用计数可以直接存储在优化过的isa指针中,也可以存储在SideTable类中)
struct SideTable{
	spinlock_t slock;//锁
	RefcoutnMap refcnts;//存放着对象引用计数的散列表
	weak_table_t weak_table;
}

当调用 allocnewcopymutableCopy 方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease释放它,想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1。

@property (nonatomic, assign)int age;
- (void)setAge:(int)age
{
    _age = age;
}
- (int)age
{
    return _age;
}

@property (nonatomic, retain)NSString *name;
- (void)setName:(int *)name
{
    if (_name != name) {
    	// 释放之前的指向资源
        [_name release];
        // 持有现在的指向资源
        _name = [name retain]
    }
}

仅堆区的数据才会使用引用计数管理内存。

2.1 TaggedPointer 内存优化

1、从64bit开始,iOS引入了 Tagged Pointer 技术,用于优化NSStringNSDateNSNumber 等小对象存储。
2、在没有使用 Tagged Pointer 之前,NSNumber 等对象需要动态分配内存、维护引用计数等,NSNumber 指针存储的是堆中 NSNumber 对象的地址值。
3、在使用 Tagged Pointer 之后,NSNumber 指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在指针中。
4、当对象真正的最高有效位数是1(iOS),最低有效位数是1(MAC),则该指针为 Tagged Pointer

//iOS平台
#define _OBJC_TAG_MASK (1UL<<63)
//Mac平台
#define _OBJC_TAG_MASK 1UL

BOOL isTaggedPointer(id pointer)
{
    return ((uintptr_t)pointer * _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}

5、当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
6、objc_msgSend 能识别到 Tagged Pointer,比如 NSNumberintValue 方法,直接从指针提取数据,节省了调用开销

2.2 对象的释放流程

当一个对象要释放时,会自动调用dealloc,调用轨迹如下:

  1. dealloc
  2. _objc_rootDealloc
  3. rootDealloc
  4. object_dispose
  5. objc_destructInstance、free
void *objc_destructInstance(id obj)
{
	if(obj){
		bool cxx = obj->hasCxxDtor();
		bool assoc = obj->hasAssociatedObjects();

		if(cxx)object_cxxDestruct(obj);//清除成员变量
		if(assoc)_object_remove_assocations(obj);//清除关联对象
		objc->clearDeallocating();//将指向当前对象的弱指针置为nil
		
	}
}

2.3 AutoreleasePool

自动释放池的主要底层数据结构是:__AtAutoreleasePoolAutoreleasePoolPage
调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
iOS - 内存管理

//每个AutoreleasePoolPage对象占用4096字节内存,处理用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
//所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
//调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
//调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
struct AutoreleasePoolPage
{
	magic_t const magic;
	id *next; //指向下一个能存放autorelease对象地址的区域
	pthread_t const thread;
	AutoreleasePoolPage *const parent;
	AutoreleasePoolPage *child;
	uint32_t const depth;
	uint32_t hiwat;
}


struct __AtAutoreleasePool {
    __AtAutoreleasePool(){ //构造函数,在创建结构体的时候调用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool(){ //析构函数,在结构体销毁的时候调用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    
    void * atautoreleasepoolobj;
};

App 下的自动释放池

iOS在主线程的Runloop中注册了2个Observer
第一个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()。
第二个Observer 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()。
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()。

可以通过 extern void _objc_autoreleasePoolPrint(void); 私有函数来查看自动释放池的情况

三、内存问题

3.1 内存溢出

App 在使用过程中,使用的内存已超过系统分配的内存,导致不够用,故称为内存溢出。

3.2 内存泄漏

3.2.1 内存类型

系统在运行的时候,都会给进程(App)分配一块内存空间(限制大小),当 App 在运行的过程中使用的内存大小大于系统分配的内存空间,将会出现 OOM(Out Of Memory) 崩溃。

App 内影响内存泄漏主要有三种类型:

  • Leaked Memory:内存没有被引用,并且也不能被重复使用或者释放掉。
  • Abandoned Memory:内存有被引用,但是不能重复使用。
  • Cached Memory:内存有被引用到,并且能被重复使用。

Clean & Dirty 内存

系统内存一般以页为单位来划分(iOS 每一页包含 16kb),一般一段数据会占用多页内存,所占用页总数乘以每页空间得到的就是总使用内存。
内存页依照占用和非占用状态将内存分为 clean 和 Dirty。

// 此时 arr 所指向的是 clean 内存(未存储数据)
int *arr = malloc(16); 
// 此时 arr[0] 所指向的是 Dirty 内存(存储数据), arr[1-3] 依然是 clean 内存(未存储数据)
arr[0] = 1;

Compressed 内存
内存不足时,系统会依据策略将优先级比较低的内存挪到磁盘上,此过程称为 Page Out。当 App 再次访问时,系统会将磁盘上的数据加载到内存空间,此过程称为 Page In。
由于频繁的的 IO 操作会降低存储设备的寿命,故后面系统都采用 Compressed 来压缩内存空间。

内存类型
clean Memory App 未使用的内存空间,包含能够 Page Out 的内存(类似于 frameworks 的 _DATA_CONST 段)
dirty Memory App 已使用的内存空间(堆区的对象,缓冲区),比如 frameworks 的 _DATA _DATA_DIRTY 段。
compressed Memory 内存吃紧时,系统会把非活跃的内存压缩,当访问该内存时会先进行解压缩(一种CPU 时间换系统 IO 时间的折中方案)

内存告警处理方案:
方案一:在 -didReceiveMemoryWarning 方法内部通过手动设置策略来清理占用内存
方案二:使用 NSCache 对象替代其他对象,将内存交由系统来管理

3.2.2 内存泄漏场景

  • Block 循环引用
// 循环引用(block 在堆区)
self.block = ^(){
	NSLog(@"%@", self.name);
}

self 持有 block 属性,当 block 被拷贝到堆区时同时也强持有 self,故会产生引用环,导致 self 不能被系统正常的释放。

【解决方法】
__weak 修饰 self 对象

  • 3.2.1 Delegate 循环引用
@interface JHProxyObject : NSObject
@property (nonatomic, strong)JHObject *object;
@end

- (instancetype)init{
	...
	// 设置代理
	self.object.delegate = self;
	...
}


@interface JHObject : NSObject
@property (nonatomic, strong)id delegate;
@end

JHProxyObject 持有 JHObject 属性,JHObject 也由于 delegate 强引用 JHProxyObject,故会产生引用环,导致 JHProxyObjectJHObject不能被系统正常的释放。

【解决方法】
weak 修饰 delegate 属性

  • 3.2.3 NSTimer 循环引用
@interface JHTimer : NSObject
@property (nonatomic, strong)NSTimer *timer;
@end

- (instancetype)init{
	...
	// 设置计时器
	self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(do) userInfo:nil repeats:YES];
	...
}

JHTimer 持有 timer 属性,timer 内部会维护一个 Runloop 循环,timer 通过 target 强持用 self, 故会产生引用环,导致 JHTimer 不能被系统正常的释放。

【解决方法】
1、timer通过 Block API 创建实例,使用 __weak 修饰 self 对象,打破引用环。
2、创建 用 JHProxyTimer 类,弱引用 self

  • 3.2.4 非 OC 对象内存处理

//GPU优化
EAGLContext * eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
eaglContext.multiThreaded = YES;

// 设置上下文
CIContext *context = [CIContext contextWithEAGLContext:eaglContext];
[EAGLContext setCurrentContext:eaglContext];

CGImage *outputImage = nil;
CGImageRef ref = [context createCGImage:outputImage fromRect:outputImage.extent];

UIImage *img = [UIImage imageWithCGImage:ref];
CGImageRelease(ref);

非 OC 对象 CGImageRef 通过 create- 的方式创建,需要调用 CGImageRelease(ref) 释放占用资源,否则内存得不到释放(Core Foundation 框架的一些对象或变量也需要手动释放,C/C++ 中一些通过开辟内存空间方法生成的指针,在使用完成后需要调用 free 函数释放掉占用资源)。

  • 3.2.5 循环创建对象
for (int i = 0; i < 100000; i ++) {
	NSString *str = @"123";
	str = [str stringByAppendingString:@"xyz"];
}

多次循环创建临时变量,由于临时变量只会在出了作用域后才开始释放占用内存资源,故如大量创建会导致在作用域内内存资源被大量占用。

【解决方法】

for (int i = 0; i < 100000; i ++) {
	@autoreleasepool {
		NSString *str = @"123";
		str = [str stringByAppendingString:@"xyz"];
	}
}

添加 @autoreleasepool, 将生成的临时变量放入自动释放池,当系统发现内存不够时会自动回收内存。

3.2.3 内存泄漏检测工具

  • FBRetainCycleDetector 工具:通过收集对象的强引用构成的有向图,并且检测有向图中是否产生环。
    • 成员变量强弱引用检测
    • NSArray / NSDictory / NSSet / NSMapTable 强弱引用检测
    • AssociationObjc 对象检测(通过 fishhook 替换函数方法)
  • MLeaksFinder 工具:检测 ViewController 在 pop or dismiss 后限定时间内本身或者承载的子视图是否被释放来判定当前是否有其他内存被占用。

MLeaksFinder 注意点:
1、全局单例对象
2、控制器释放时机问题,比如右滑返回中途等

3.2.4 iOS OOM 执行流程

iOS 通过 Jetsam 机制开启进程来监控系统出现的 OOM ,它是通过 Signal 捕获等 Crash 监控方案无法捕获到的 OOM 事件(流程如下)。
第一步:Jetsam 机制初始化完毕,从外部接收到内存压力
第二步:接收到内存压力是当前物理内存达到限制时,同步触发 per-process-limit 类型的 OOM ,退出流程。
第三步:接收到内存压力是其他类型时,唤醒 Jetsam 线程,判断可用内存是否小于阀值,进入 OOM
第四步:遍历优先级最低的每个进程,判断当前进程是否高于阀值,直到找到触发内存 high-water 类型的 OOM
第五步:回收触发内存 high-water 类型的 OOM后,继续第四步操作。
第六步:所有低优先级的进程被回收后,再判断当前内存是否小于阀值,如果依然大于,则继续杀掉后台进程,每杀掉一个进程,判断一下当前内存是否小于阀值,如果小于则挂起线程。
第七步:当所有后台进程被杀掉后,继续杀掉前台的进程,挂起线程,等待唤醒。
第八步:如果前七步未杀掉任何线程,就通过 LRU 杀掉 Jetsam 队列中的第一个进程,挂起线程,等待唤醒。文章来源地址https://www.toymoban.com/news/detail-426049.html

到了这里,关于iOS - 内存管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • iOS - 内存管理

    iOS 中,使用引用计数来管理 OC 对象的内存,新创建的 OC 对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。调用 retain 会让 OC 对象的引用计数 +1,调用 release 会让 OC 对象的引用计数-1。 当调用 alloc 、 new 、 copy 、 mutableCopy 方法返回了一个对

    2023年04月26日
    浏览(24)
  • iOS 内存管理和优化

    对内存管理和拓展有独特的描述 iOS学习-内存管理 比较详细说明内存的关系 iOS 内存管理机制与原理 iOS 内存泄漏排查方法及原因分析 对weak的实现原理描写详细 【iOS】—— weak的基本原理 iOS copy mutableCopy iOS 深拷贝与浅拷贝 对iOS的浅复制和深复制的深入解释 【iOS】深拷贝与浅

    2024年02月07日
    浏览(36)
  • 【iOS】ARC内存管理

    怎么说呢。经典再放送咯。 对象操作 对应的Objective-C方法 生成并持有对象 alloc/new/copy/mutableCopy等方法 持有对象 retain方法 释放对象 release方法 废弃对象 dealloc方法 iOS内存管理方案有三种,我们详细看下每种方案的实现及存在的意义。 标签指针 没有这种管理机制会引起内存浪

    2024年02月16日
    浏览(35)
  • 近4w字吐血整理!只要你认真看完【C++编程核心知识】分分钟吊打面试官(包含:内存、函数、引用、类与对象、文件操作)

    🌈个人主页:godspeed_lucip 🔥 系列专栏:C++从基础到进阶 🏆🏆关注博主,随时获取更多关于C++的优质内容!🏆🏆 🍉配套markdown文件下载:请翻阅至文章底部获取 本阶段主要针对C++ 面向对象 编程技术做详细讲解,探讨C++中的核心和精髓。 C++程序在执行时,将内存大方向划

    2024年01月17日
    浏览(34)
  • 【iOS内存管理-编译链接的过程】

    就我而言,iOS开发的过程中接触到的编译链接方面的知识很少,这部分知识还是很重要的。 对于iOS的编译链接过程来说并不难,和微机原理的汇编过程还是挺像的。今天对于编译链接的过程学习和了解一下。 参考:iOS程序员的自我修养-编译、链接过程 参考:iOS编译过程 计

    2024年02月16日
    浏览(32)
  • LabVIEW使用数据引用减少内存

    概览 NI LabVIEW 省略了 开发 软件时 需要 手动 管理 内存。LabVIEW 编译器 始终 会 分析 您 的 代码, 以 确定 如何 优 化 性能 并 减少 所需 的 内存 量。但是, 想要 更多 控制 内存 分配 的 高级 用户 可以 在 LabVIEW 2009 中创建 数据 引用。 内容 背景 基本示例 对类使用引用 其他

    2024年02月16日
    浏览(31)
  • Unity3d:GameFramework解析:实体,对象池,资源管理,获取计数,引用计数,自动释放

    1.GF万物基于引用池IReference 2.ObjectBase : IReference类的m_Target持有unity中Mono,资源,GameObejct 3.AssetObject : ObjectBase类m_Target持有Assetbundle中的Asset,具有获取,引用两个计数管理释放 4.ResourceObject : ObjectBase类m_Target持有Assetbundle,具有获取,引用两个计数管理释放 5.EntityInstanceObject :

    2024年02月11日
    浏览(22)
  • 【C++初阶】七、内存管理(C/C++内存分布、C++内存管理方式、operator new / delete 函数、定位new表达式)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】六、类和对象(初始化列表、static成员、友元、内部类)-CSDN博客  ==================

    2024年02月05日
    浏览(32)
  • 【C++基础(九)】C++内存管理--new一个对象出来

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C++从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C++   🔝🔝 在C语言中,有四个内存管理函数: malloc,calloc,realloc和free 但是它们的使用十分的不方便: 代码量很大,并且有一个新的问题: malloc函数不会初始

    2024年02月14日
    浏览(47)
  • 进一步了解C++函数的各种参数以及重载,了解C++部分的内存模型,C++独特的引用方式,巧妙替换指针,初步了解类与对象。满满的知识,希望大家能多多支持

    C++的编程精华,走过路过千万不要错过啊!废话少说,我们直接进入正题!!!! 函数默认参数 在C++中,函数的形参列表中的形参是可以有默认值的。 语法 : 返回值类型 函数名 (参数 = 默认值){} 示例 : 函数占位参数 C++中函数的形参列表里可以有占位参教,用来做占位

    2023年04月17日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包