本文为L_Ares个人写作,以任何形式转载请表明原文出处。
首先,要知道iOS
的内存管理,还是要具备常规操作系统的基本内存知识的。这些基础的概念型知识,无论是PC
、移动端
还是什么blabla的,都是一个最基层的东西,概念如果搞不清楚,很难去理解iOS
的内存管理。
一、内存的基础知识
1. 什么是内存
内存是用于存放数据的硬件。
2. 内存能做什么
程序(也就是你的代码)是要放到内存中才可以被
CPU
处理的。
3. 内存的基本存储单元
在内存中,信息都是以二进制形式进行存储、运算、处理和传输的。信息的存储单位有
位(bit)
、字节(byte)
和字(word)
等等几种单位。
-
位(bit)
: 计算机中数据的最小单位
。二进制数中的一个数位,可以是0或者1。 -
字节(byte)
: 计算机中数据的基本单位
。每8位(bit)
组成一个字节(byte)
,也就是常说的 :1byte = 8bit
。各种信息或者说数据在计算机中进行存储或者处理,至少需要1字节
。 -
字(word)
: 两个字节称为一个字。常见的汉字就是一字。1word = 2byte = 16bit
。

4. 地址
地址就是内存的索引编号,通过这个索引编号,就可以存取对应内存的数据。
地址一般分为 : 物理地址
和逻辑地址
。
物理地址(绝对地址)
: 数据实际在内存中存放的地址。
逻辑地址(相对地址)
: 实际生成指令的时候,数据不一定会被分配到哪里去,所以编译生成的指令中常用的就是逻辑地址。
二、内存五大区
先上图,看一下五大区是什么样子,是做什么的。

1. 栈区
栈区是内存模型的叫法,栈是数据结构,因为本节是说内存管理的一些基本知识,就只说栈区,不说栈了,栈会在数据结构相应的章节中再说。
1.1 栈区特性
- 栈区是
内存模型
,是一块连续
的内存区域,在运行时分配
。- 内存中,栈区处于
相对较高的地址
,并且由高地址
向低地址
扩展。- 栈区的
栈顶地址
和栈区容量
都是由系统定好的。具体大小是在进程分配时确定的,栈区内存是由编译器自动分配和释放的
,所以栈区是不由我们控制的。- 栈区主要用来存放的是
函数的参数值
、局部变量
、OC中对象的指针
等。- 在
iOS
中,栈区的地址空间一般以0x7
开头。
1.2 栈区优缺点
- 优点 :
- 栈区内存由于是内置于系统CPU的指令集自动分配和释放,也就是说用寄存器来操作,所以
效率高
,速度快
。- 缺点 :
- 栈区可分配的内存大小是
有限的
,并且因为不由我们操控,所以数据不灵活
。
比如 :iOS主线程
的大小只有1M
。其他的二级线程
大小只有512K
,即使是Mac
,也只有8M
。
2. 堆区
堆区也是内存模型的叫法,堆是数据结构,所以还是只说堆区,堆放在数据结构中说。
2.1 堆区特性
- 堆区是
内存模型
,是不连续
的内存区域,在运行时分配
。- 堆区相对处于
较低的地址
,堆区是由低地址
向高地址
扩展的。- 堆区的大小是
动态的
。相比栈区要大一些。堆区是手动进行分配和释放的,如果分配后没有手动释放,可能会由系统回收,如果系统没有回收,则会造成内存泄漏。- 堆区在
OC
中主要用来存放alloc
或new
开辟空间时创建的对象,还有block
、copy
的数据。在C
中则是malloc、calloc、realloc
分配的内存空间。- 在
iOS
中,堆区的地址空间一般以0x6
开头。
2.2 堆区优缺点
- 优点 :
- 使用起来方便,相对大一点,数据应用面也广一点。
- 缺点 :
速度慢
,因为找一个空闲的堆区要遍历整个系统中记录空闲内存地址的链表。找到第一个空间大于所申请空间的堆结点, 然后把它从空闲内存地址的链表中删除,然后才将该结点的内存空间分配给程序。手动管理
,手动管理就容易产生内存碎片。
2.3 堆区栈区的联合使用
这个其实就是在访问某一个对象的时候,一般先拿
对象在栈区的指针地址
,然后通过对象的指针地址再去堆区找对象的实际地址
。
3. 静态区(全局区)
静态区或者说全局区,它们是一个东西,因为静态变量和全局变量都存储在这里。静态区(全局区)是编译时分配内存空间
。
说几点特性 :
- 在程序的运行过程中,静态区(全局区)的数据是一直都存在的,要等到程序结束后,由系统来释放。
- 未初始化的静态变量和全局变量在一块区域,被叫做
BSS区(.bss)
.- 初始化的静态变量和全局变量在未初始化的相邻区域,被叫做
数据区(.data)
。- 静态变量作用域和全局变量不同而已,生命周期相同,全局变量作用域是整个项目文件,除了不包括全局变量定义的文件。静态变量的作用域则只是定义了它的文件,并且只初始化一次就一直会到生命周期结束。
4. 常量区
常量区也是编译时分配内存空间
,也是程序结束后,由系统释放
。
- 顾名思义,常量区存放的都是常量,一般也被叫做
.rodata区
,这也就限制了它的能力,常量吗,是不让你改的。- 比如说你常用的一些
字符串常量
,它们不需要被更改,可能会被你多次使用,所以在程序运行之前,就可以给他们分配内存空间了。iOS
中常量区一般以0x1
开头。
5. 代码区
代码区也是编译时分配内存空间
,一般也被叫做.text区
,存放的就是程序运行时的代码,代码会编译成二进制存放入代码区。
6. 例子

-
number
: 局部变量。地址一般以0x7
开头,存放在栈区
。 -
str
: 字符串常量对象- 对象本身 :
str
就是个字符串常量对象,地址一般以0x1
开头,存放在常量区
。 - 对象的指针 :
str
是个对象,有指针,地址一般以0x7
开头,存放在栈区
。
- 对象本身 :
-
obj
: 普通对象- 对象本身 : 地址一般以
0x6
开头,存放在堆区
。 - 对象的指针 : 地址一般以
0x7
开头,存放在栈区
。
- 对象本身 : 地址一般以
三、举例
放一些例子吧,一些虽然比较老,但是很经典的例子,基本上看过这些例子,能玩的转的,就可以说对内存的五大区有了一个初步的掌握了。从上到下的举例,上面已经有了关于堆区、栈区的一个简单的例子了,就不说堆区、栈区了,从静态区(全局区)来举例。
静态区(全局区)
这里我发现很多人是拎不清静态是不是允许改变的,没有人说普通的静态变量或者全局变量不允许改变,看好它们的名字——变量
!变量怎么可能不允许变呢。但是变的是哪个量,要看文件!看文件!看文件!文件才是它的作用域限制。
准备 :
(1). 创建一个Project
--->App
(2). 创建一个继承于NSObject
的JDPerson
类,再创建一个JDPerson
的分类JDPerson+JD
。
(3).ViewController
中引入头文件,JDPerson.h
和JDPerson+JD.h
。
JDPerson.h
#import <Foundation/Foundation.h>
static int jd_person_num = 100;
NS_ASSUME_NONNULL_BEGIN
@interface JDPerson : NSObject
- (void)study;
+ (void)eat;
@end
NS_ASSUME_NONNULL_END
JDPerson.m
#import "JDPerson.h"
@implementation JDPerson
- (void)study
{
jd_person_num++;
NSLog(@"\n JDPerson内部 *** *** 地址 : %p --- 内容 : %d \n",&jd_person_num,jd_person_num);
}
+ (void)eat
{
jd_person_num++;
NSLog(@"\n JDPerson内部 *** *** 地址 : %p --- 内容 : %d \n",&jd_person_num,jd_person_num);
}
@end
JDPerson+JD.h
#import "JDPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface JDPerson (JD)
- (void)jd_category_instance_method;
@end
NS_ASSUME_NONNULL_END
JDPerson+JD.m
#import "JDPerson+JD.h"
@implementation JDPerson (JD)
- (void)jd_category_instance_method
{
NSLog(@"\n JDPerson+JD 内部 *** *** 地址 : %p --- 内容 : %d \n",&jd_person_num,jd_person_num);
}
@end
ViewController.m
- (void)jd_mem_static
{
JDPerson *person = [[JDPerson alloc] init];
//看JDPerson中定义的静态变量
NSLog(@"\n viewController 中 *** *** 地址 : %p --- 内容 : %d \n",&jd_person_num,jd_person_num);
//改变JDPerson中定义的静态变量
jd_person_num = 888;
NSLog(@"\n viewController 中 *** *** 地址 : %p --- 内容 : %d \n",&jd_person_num,jd_person_num);
//JDPerson自己内部查看静态变量
[person study];
//对比看一下ViewController中的静态变量
NSLog(@"\n viewController 中 *** *** 地址 : %p --- 内容 : %d \n",&jd_person_num,jd_person_num);
//JDPerson调用类方法,再查看内部的静态变量
[JDPerson eat];
//对比看一下ViewController中的静态变量
NSLog(@"\n viewController 中 *** *** 地址 : %p --- 内容 : %d \n",&jd_person_num,jd_person_num);
//JDPerson分类中拿到的静态变量
[person jd_category_instance_method];
}
结果 :

结论 :
静态变量在不同的文件中是不一样的,自己玩自己的。静态全局变量,作用域是定义它的文件的内部,自己文件内部都一样,生命周期是程序结束。
这篇只是内存管理的序篇,因为内存管理中还要接触到包括线程、锁、block等等在内的东西,所以其他的章节会放到这些东西都说明白了以后再说,这样更容易理解。
网友评论