news 2026/9/28 20:14:20

内存管理(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
内存管理(一)

内存管理(一)

文章目录

  • 内存管理(一)
    • 内存五大区
      • 栈区
      • 函数栈
      • 堆区
      • 全局区(.bss & .data)
      • 常量区
      • 代码区
    • 内存管理
      • 内存优化方案
        • Tagged Pointer
        • NONPOINTER_ISA
        • SideTable
      • MRC和ARC
        • MRC
        • ARC

内存五大区

栈区

栈区(stack):编译器自动分配,由系统管理,在不需要的时候自动清除。局部变量,函数的参数储存在这里。栈区的内存地址一般是0x7开头,从高地址到低地址分配内存空间

在iOS中,栈是每个线程独立的,如果创建了一个子线程,子线程也有它自己的栈。因此线程之间不能简单的通过局部变量来直接共享数据

voidA{[selfB];}voidB{[selfC];}voidC{}

执行过程是C->回调B->回调C,这就是栈,后进先出,不断递归

iOS主线程大小为1MB 其他线程为512KB MAC为8MB

通过上面的数据我们发现栈区的空间其实比较小,因此递归层级过深或者过多的局部变量,因此我们需要防止栈区内存溢出

函数栈

函数栈在内存中从高地址网低地址分配,与堆区相对

栈帧是指函数(运行中未完成)占用的一块独立的连续内存区域

函数共享线程的这个栈空间,每个函数使用的栈空间是一个栈帧,所有的栈帧组成这个线程完整的栈

函数调用发生在栈上,每个函数的相关信息(局部变量,函数参数,调用记录等)都储存在这个栈帧中,每执行一次函数调用,就会生成一个与其相关的栈帧,然后栈帧压入函数栈,当函数执行结束,就将函数对应的栈帧出栈释放掉

Caller:调用者(图中的main函数),callee:被调用者(图中的func1函数)

如图中所示在执行func1前,目前只有main栈帧,此时可以简单理解为:

高地址-> main栈帧【argc argv i = 10 j = 5】 -> 低地址

然后main调用func1,此时CPU从main跳到func1,但并不能简单的跳转,因为func1执行完后还要知道应该回到main的哪里继续执行,所以函数的调用需要保存各种上下文

PC:PC就是一个程序计数器,它表示CPU当前正在执行/即将执行哪里的指令,还是拿图中举例,当执行main函数时,我们在哪个指令PC就指向哪个指令,当我们执行func1时,CPU做了两件事:将返回地址也就是fun1();的下一个指令压入栈,然后将func1的入口地址写入PC,然后就跳到函数中执行,当函数执行完后,从栈里弹出之前保存的返回地址,把这个地址写入PC

LR:LR就是储存返回地址的指针,但是LR只有一个,当我们有多重函数调用时,一个LR就保存不过来了,因此在多重函数调用时,我们的逻辑时,如果LR中没有返回地址,那么就将返回地址存入,如果已经有了,那么现将这个LR压入栈,再将新地址写入CPU中的LR

SP:SP保存当前栈顶的地址,在ARM64栈模型中,栈往低地址方向增长,所以当需要更多栈空间时,SP向低地址移动,SP 的变化反映了当前函数需要的栈空间。

FP:SP可能因为保存寄存器,分配局部变量等发生变化,此时如果依赖SP定位可能比较麻烦,此时可以使用FP当前栈帧的一个固定参考位置,去定位栈帧中的相关数据,并且FP还会保存上一个FP所在的位置

堆区

堆区:堆是从低地址向高地址扩展的数据结构,堆是不连续的一片内存区域,类似与链表(便于增删,不便于查询),遵循先进先出原则,堆的地址空间在iOS是以0x6开头,其空间的内存分配是动态的,堆区的分配一般在运行时分配

堆区是由程序员动态分配和释放的,如果程序员不释放,可能由操作系统回收

OC中用alloc或者使用new开辟空间创建对象

C语言中使用malloc,calloc,realloc分配的空间,需要free释放

优点:灵活方便,数据适应面广泛

缺点:需手动管理,速度慢,容易产生内存碎片

当需要访问堆中内存时,一般需要先通过对象读取到栈区的指针地址,然后通过指针地址访问堆区

全局区(.bss & .data)

全局区是编译时分配的内存空间,在iOS中一般以0x1开头,在程序运行过程中,此内存中的数据一直存在,程序结束后由系统释放,主要存放

未初始化的全局变量和静态变量,即BSS区(.bss)

已初始化的全局变量和静态变量,即数据区(.data)

其中,全局变量是指变量值可以在运行时被动态修改,而静态变量是 static 修饰的变量,包含静态局部变量和静态全局变量

常量区

常量区是 编译时分配的内存空间,在程序结束后由系统释放,主要存放 已经使用了的,且没有指向的字符串常量

字符串常量因为可能在程序中被多次使用,所以在程序运行之前就会提前分配内存

代码区

代码区是 编译时分配主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的

内存管理

移动端的内存管理技术,主要有GC(垃圾回收)的标记清除算法,以及苹果公司使用的引用计数方法

相比较于 GC 标记清除算法,引用计数法可以及时地回收引用计数为 0 的对象,减少查找次数。但是,引用计数会带来循环引用的问题,比如当外部的变量强引用 Block 时,Block 也会强引用外部的变量,就会出现循环引用。我们需要通过弱引用,来解除循环引用的问题。

另外,在 ARC(自动引用计数)之前,一直都是通过 MRC(手动引用计数)这种手写大量内存管理代码的方式来管理内存,因此苹果公司开发了 ARC 技术,由编译器来完成这部分代码管理工作。但是,ARC 依然需要注意循环引用的问题。当 ARC 的内存管理代码交由编译器自动添加后,有些情况下会比手动管理内存效率低,所以对于一些内存要求较高的场景,我们还是要通过 MRC 的方式来管理、优化内存的使用

内存优化方案

Tagged Pointer

在使用Tagged Pointer前,如果声明一个NSNumber number = @10;变量,需要8个字节存指针变量number,需要16个字节存NSNumber对象,这样需要24个字节的内存空间

而使用Tagged Pointer后,NSNumber指针里存储的数据变成了:Tag + Data, 也就是将数据直接存入了指针,直接将数据10保存在指针变量number中,这样仅仅占用8个字节

但是当指针不够存储数据时,就会使用动态分配内存的方法来存储数据

dispatch_queue_t queue=dispatch_get_global_queue(0,0);for(inti=0;i<1000;i++){dispatch_async(queue,^{self.name=[NSString stringWithFormat:@"asdasdefafdfa"];});}NSLog(@"end");
dispatch_queue_t queue=dispatch_get_global_queue(0,0);for(inti=0;i<1000;i++){dispatch_async(queue,^{self.name=[NSString stringWithFormat:@"abc"];});}NSLog(@"end");

上面两段代码的结果是,第一段会崩溃,但第二段不会

创建多个线程来对name进行操作时,name的引用计数会不断的retain和release,此时就会出现资源竞争而崩溃,但第二段却不会崩溃,说明在Tagged Pointer下,较小的值不会调用set和get方法,可以直接修改,关于字符串什么时候是用Tagged Pointer可以见我之前的文章NSString类簇

通过源码,我们也可以比较直观的看出,Tagged Pointer类型对象是直接返回的。

在retain和release的源码中,我们都可以发现第一句就是这个,它表示Tagged Pointer根本不进引用计数逻辑,直接返回自己,不操作isa

if(slowpath(isTaggedPointer()))return(id)this;

Tagged Pointer是专⻔⽤来存储⼩的对象,例如NSNumber,NSDate等。

Tagged Pointer指针的值不再是地址了,⽽是真正的值。所以,实际上它不再是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。所以,它的内存并不存储在堆中,也不需要malloc和free。

当指针不够存储数据时,就会使用动态分配内存的方式来存储数据。

NONPOINTER_ISA

NONPOINTER_ISA也是苹果内存优化的一种方案,用八字节来存储一个内存地址显然有些浪费,于是可以用一部分额外空间存储其他内容,isa指针第一位为1即表示使用优化的isa指针

unionisa_t{isa_t(){}isa_t(uintptr_t value):bits(value){}Class cls;uintptr_t bits;#ifdefined(ISA_BITFIELD)struct{uintptr_t nonpointer:1;uintptr_t has_assoc:1;uintptr_t has_cxx_dtor:1;uintptr_t shiftcls:44;/*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/uintptr_t magic:6;uintptr_t weakly_referenced:1;uintptr_t deallocating:1;uintptr_t has_sidetable_rc:1;uintptr_t extra_rc:8};#endif};

下面解释一下isa结构体中各个变量的含义

  • nonpointer:表示是否对isa指针开启指针优化 0:纯isa指针,1:不只是类对象地址,isa中包含了类信息,对象的引用计数等
  • has_assoc:关联对象标志位:0没有,1存在
  • has_cxx_dtor:该对象是否有C++或者Objc的析构函数,有要做析构逻辑,没有可以更快的释放对象
  • shiftcls:储存类指针的值,开启指针优化的情况下,在arm架构有33位用来储存
  • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间
  • weakly_referenced:指对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放
  • deallocating:标志对象是否正在释放内存
  • has_sidetable_rc:当对象引用计数大于10时,则需要借用该变量存储进位
  • extra_rc当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数值为10,则extra_rc为9.如果引用计数大于10则要用到has_sidetable_rc
SideTable

在NONPOINTER_ISA有两个成员变量hsa_sidetable_rc和extra_rc,当extra_r的八位不够存储引用计数时,hsa_sidetable_rc的值就会变成1,那么此时引用计数会存储在SideTable中

SideTable可以理解为一个全局的hash数组,里面存储了SideTable类型的数据,长度为64,也就是里面有64个SideTable

structSideTable{spinlock_t slock;// 自旋锁,保护这张表的读写RefcountMap refcnts;// 引用计数表(专门存那些塞不下的计数)weak_table_t weak_table;// 弱引用表(存所有 __weak 指针的地址)};
  • slock:因为多线程可能同时操作,需要一把锁来保证线程安全。用自旋锁是因为大多数操作都很快,忙等一会儿比让出 CPU 更划算。
  • refcnts:一个哈希表。Key 是对象的地址,Value 是引用计数的具体数值。当isa里的extra_rc溢出时,一半的计数会存到这里。
  • weak_table:同样是个哈希表。Key 是被弱引用的对象地址,Value 是一个数组,里面装着所有指向该对象的__weak指针的地址。对象销毁时,就靠这张表把这些弱引用指针都置为nil

MRC和ARC

MRC

在MRC时代,程序员需要手动管理内存,创建一个对象时,需要在set和get方法内部添加释放对象的代码,并且在对象的dealloc里面添加dealloc里面添加释放的代码

@property(nonatomic,retain)NSString*name;-(void)setName:(NSString*)name{if(_name!=name){[_name release];// 释放旧值_name=[name retain];// 持有新值}}-(NSString*)name{return_name;}
-(void)dealloc{[_name release];[_age release];[superdealloc];// 必须最后调用}
ARC

在ARC环境中,我们不再像以前一样自己手动管理内存,系统帮助我们做了release或者autorelease等事情。 ARC是LLVM编译器和RunTime协作的结果。其中LLVM编译器自动生成release、reatin、autorelease的代码,像weak弱引用这些则靠RunTime在运行时释放

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 20:14:19

灵活用工是什么,为什么企业降本增效里离不开它

灵活用工是什么&#xff0c;为什么企业降本增效里离不开它你是不是也遇到过这样的困境&#xff1f;大促期间订单暴增&#xff0c;临时招了一批兼职客服或主播&#xff0c;但发工资时却犯了难&#xff1a;直接转账怕税务风险&#xff0c;走劳务合同又流程繁琐&#xff1b;或者想…

作者头像 李华
网站建设 2026/9/28 20:14:19

摘掉手环!黎阳之光视频孪生无感定位,破解港口堆场人车管控难题

港口堆场作业环境复杂&#xff0c;大型集装箱、重型机械往来穿梭&#xff0c;外来施工、集卡司机、现场运维人员流动频繁&#xff0c;安全监管压力巨大。很多港口项目采用UWB手环定位方案落地&#xff0c;落地后却痛点频发&#xff1a;作业人员忘记佩戴手环&#xff0c;直接脱离…

作者头像 李华
网站建设 2026/9/28 20:14:18

BERT-BiLSTM-CRF中文命名实体识别实战:从原理到避坑指南

简介&#xff1a;面向中文命名实体识别任务的完整Python工程&#xff0c;融合BERT-BILSTM-CRF模型&#xff0c;可满足毕业设计、课程设计或项目实践需求。压缩包内共二十个文件&#xff0c;涵盖六份Python脚本、八份JSON配置、五份TXT数据/标签文件与一份Markdown说明&#xff…

作者头像 李华
网站建设 2026/9/28 20:13:57

linux定时备份mysql数据库

新建文件 mysql_backup.sh&#xff0c;将文件放到home目录下&#xff08;具体位置可以自己选择&#xff09;&#xff0c;文件内容如下&#xff1a;#!/bin/bash#备份目录 BACKUP/mnt/mysql_backup#当前时间 DATETIME$(date %Y-%m-%d_%H%M%S)#数据库地址 视情况而定&#xff0c;如…

作者头像 李华
网站建设 2026/9/28 20:13:39

2026数据可视化校招JD复盘:ECharts、SQL、Three.js的权重变化

2026年数据可视化校招的核心要求集中在三个方向&#xff1a;前端可视化框架&#xff08;Vue3 ECharts为最常见组合&#xff09;、数据层能力&#xff08;SQL Python&#xff09;、以及业务交互设计&#xff08;钻取/联动/筛选&#xff09;。以下基于2026年校招JD和面经逐条展…

作者头像 李华