iOS - 内存管理

news2024/12/24 1:04:41

一、App 内存分布

在这里插入图片描述

二、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对象来管理的
在这里插入图片描述

//每个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 MemoryApp 未使用的内存空间,包含能够 Page Out 的内存(类似于 frameworks 的 _DATA_CONST 段)
dirty MemoryApp 已使用的内存空间(堆区的对象,缓冲区),比如 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 队列中的第一个进程,挂起线程,等待唤醒。

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

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

相关文章

perf生成火焰图

文章目录 1&#xff0c;top发现webserver进程空转情况下CPU占用高达200%2&#xff0c;使用性能分析工具perf来进行分析2.1&#xff0c;抓取采集样本2.2&#xff0c;使用perf简单分析性能数据 3&#xff0c;火焰图3.1&#xff0c;生成火焰图3.2&#xff0c;将生成的.svg文件用浏…

电视盒子哪个好?数码小编盘点2023电视盒子排行榜

随着网络剧的热播&#xff0c;电视机又再度受宠&#xff0c;电视盒子也成为不可缺少的小家电。但面对复杂的参数和品牌型号&#xff0c;挑选时不知道电视盒子哪款最好&#xff0c;小编根据销量和用户评价整理半个月后盘点了电视盒子排行榜前五&#xff0c;对电视盒子哪个好感兴…

python编程课后练习答案:一批书几天能买完,勾股数组,个位数字与十位位数字之和除以10所得余数刚好是其百位,剪刀、石头、布猜拳游戏

一、编程题目 编程题目&#xff1a; 4、已知有一批书共1020本&#xff0c;以后每天都买掉一半还多2本&#xff0c;设计程序求出几天能买完。 5、凡是满足x^2y^2z^2的正整数数组(xYz)就称为勾股数组(如345)。请找出任意一个正整数n以内的所有勾股数组 6、编写代码找出满足下面…

不懂如何搭建Web自动化测试环境?这篇文章教你如何上手

摘要&#xff1a; 本文将介绍如何搭建Web自动化测试环境&#xff0c;使用的工具包括Selenium WebDriver和JUnit。同时&#xff0c;本文还提供了详细的代码示例&#xff0c;帮助读者更好地理解和实践相关的知识。 目录 一、前言 二、环境配置 1.安装JDK 2.安装Eclipse 3.下…

对话AI顶尖大牛周明老师:大模型的机遇和挑战?

Datawhale学习 分享人&#xff1a;周明老师&#xff0c;Datawhale 特邀嘉宾 这次 Datawhale开源学习 特别邀请了周明老师分享&#xff1a; 周明&#xff0c;澜舟科技创始人兼CEO&#xff0c;中国计算机学会 CCF 副理事长、NLP和大模型领域的顶尖大牛。 1991年就从NLP重镇哈工大…

typeScript安装以及typeScript配置

1. 什么是typeScript ? 它强调了 TypeScript 的两个最重要的特性——类型系统、适用于任何规模。 包含js的所有的元素&#xff0c;能运行js代码&#xff0c;支持ES语法&#xff0c;是一种开源、跨平台的编程语言。就是js的超 集。 TypeScript 是添加了类型系统的 JavaScri…

Java并发编程 —— ThreadLocal详解

一、什么是ThreadLocal ThreadLocal用于提供线程内部共享的变量&#xff0c;每个线程在访问ThreadLocal实例的时候都可以获得自己的、独立初始化的变量副本&#xff0c;这样线程间互不干扰&#xff0c;从而避免了线程安全问题。 比如我们知道SimpleDateFormat是线程不安全的&…

LVS负载均衡+keepalived高可用

准备准备五台虚拟机 192.168.255.128 MASTER 192.168.255.134 BACKUP 192.168.255.130 Nginx节点服务器1 192.168.255.131 Nginx节点服务器2 192.168.255.132 客户端验证 一、配置节点服务器 1、配置虚接口lo:0 cd /etc/sysconfig/network-scripts/ cp ifcfg-…

Vue-cli 3.x 脚手架搭建的 Vue 2.x 项目进行 npm install 安装时报错: npm ERR! code 1……

项目场景&#xff1a; 公司以前做过的项目&#xff0c;当时开发环境 npm&#xff0c;node.js 版本和现在都不一样&#xff0c;比较旧了&#xff0c;项目之前是用 Vue-cli 3.x 搭建的&#xff0c;Vue 2.x 写的&#xff0c;当时配套的第三方依赖版本也都很落后了&#xff0c;在近…

有趣有爱有温度!迅镭激光第一季度户外团建活动圆满落幕!

阳春四月&#xff0c;元气复苏 凝心聚力&#xff0c;共享喜悦 迅镭激光第一季度寿星及新人 欢聚常熟蒋巷基地 开启一段美妙的户外团建之旅 无创意不团建!蓝天白云下&#xff0c;队员们在教练的指导下解锁各种花样游戏大玩法&#xff0c;大家密切配合、相互协作&#xff0c;…

Docker | 解决docker 容器中csv文件乱码的情况

问题描述&#xff1a;在Ubuntu docker容器中&#xff0c;打开.csv文件时显示乱码 问题如图 错误分析&#xff1a; 用locale查看所用容器支持的字符集 从输出可以看到&#xff0c;系统使用的是POSIX字符集&#xff0c;POSIX字符集是不支持中韩文的&#xff0c;而UTF-8是支持中…

054:cesium加载WMS规范的影像服务

第054个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中加载WMS规范的影像服务。WebMapServiceImageryProvider提供由 Web 地图服务 (WMS) 服务器托管的平铺图像。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代…

剑指 Offer II 026. 重排链表

思路&#xff1a; &#xff08;1&#xff09;找到链表中心点&#xff0c;如果链表节点为奇数&#xff0c;那么要保证前面要比后面多一个节点。 &#xff08;2&#xff09;将后一部分的结点进行反转。 &#xff08;3&#xff09;将反转后的结点插入前一部分的结点。 &#xff0…

前有谷歌的“生存指南”,后有金山系的“表格编程”,均登热榜

谷歌的“生存指南” 一位曾经在谷歌工作的工程师&#xff0c;干了一件了不起的事&#xff0c;花费了两年的时间&#xff0c;整理了一份“xg2xg”的清单。 原来这位离职的谷歌工程师为程序员编写了一份“厂外生存指南”&#xff0c;即使你从谷歌离职后&#xff0c;在这套“生存…

无良公司把我从上家挖过来,白嫖了六个月,临近试用期结束才说不合适,催我赶紧找下家!...

职场套路多&#xff0c;一不小心就会掉坑&#xff0c;一位网友讲述了自己的遭遇&#xff1a; 今天被领导催促离职了&#xff0c;当时就是这个领导把他从别的公司挖过来。这家公司催得太急&#xff0c;为了投奔这里&#xff0c;他和上家的HR都闹翻了&#xff0c;上家总监挽留他&…

ChatGLM ptuning 的实战方案

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

【中间件】kafka

目录 一、概述二、生产者1. 发送原理2. 生产者分区 Partition分区好处分区策略 3. 生产者如何提高吞吐量4. 数据可靠性ACK应答级别数据不丢失&#xff1a;ACK ISR数据不重复&#xff1a;幂等性数据有序 三、broker1. 工作流程2. 副本相关3. 底层存储4. 高效读写数据 四、消费者…

常见误区,你可曾踩过?深度剖析WEB自动化测试实施问题与解决方案

目录 摘要&#xff1a; 一、WEB自动化测试实施的步骤 1.测试计划 2.测试用例设计 3.环境搭建 4.脚本编写 5.执行测试 6.缺陷管理 二、WEB自动化测试常见误区 1.选择不合适的自动化测试工具和框架 2.忽略测试环境的影响 3.. 缺乏对页面元素的理解 三、示例代码 结论…

学顶教育:一级消防师资格证发放问题分享

1、为什么证书不申请没加注就可以下载&#xff1f; 2021年12月17日前由相关行业协会、学会或有关部门指定的机构出具的电子证书&#xff0c;目前无需申请加注&#xff0c;可直接查看、下载。 2、如何查询“证书查询验证范围”&#xff1f; 进入中国人事考试网首页&#xff0…

体验 Google Bard

环境 windows 10 64bitGoogle Bardpython 3.8 简介 本篇介绍一个开源的 Google 聊天机器人Bard 的 API 逆向工程&#xff0c;使用它&#xff0c;可以免费的使用 Bard 服务&#xff0c;项目地址&#xff1a;https://github.com/acheong08/Bard 安装及使用 通过 pip 来安装 pip &…