iOS内存管理笔记

如果有不好的地方或者不全面的地方请留言批评指正,拜谢~~~


引发反思
栈怎么清除?会引发什么状况?怎么使栈溢出?
堆空间怎么清除?会引发什么状况?怎么使堆溢出?

Class A{

    NSString *_name;
    NSString *_xxxx;
    int       _age;
    A        *superClass;
}
A *a = [[A alloc] init];

内存是这么分配的?
a代表什么?

内存管理总结

内容分为虚拟内存+物理内存,正常初始化空间分配的都是虚拟内存,初始化实例才会占用物理内容
Instrument的Allocations工具可以监控
All Heap & Anoymous VM代表虚拟内存
Dirty Memory,resident Size就是物理内存的大小

访问内存其实都是访问的逻辑地址,需要转换后才能访问物理内存,
CPU根据逻辑地址通过界限寄存器判断是否越界,越界地址错误,反之加上基址寄存器转换成物理内存地址

iOS内存过多而被Kill掉?基于什么原则?

iOS使用的是低内存处理机制Jetsam,基于优先级队列的机制。
当内存过低的时候,就会在队列中进行广播,希望大家尽量释放内存,如果一段时间后,仍然内存不够,就会开始Kill进程,直到内存够用。

介绍下内存的几大区域?

1、栈:局部变量(基本数据类型、指针变量)当期作用域执行完毕之后,就会被系统立即收回(无需程序员管理(分配地址由高到低分配))

2、堆:程序运行的过程中动态分配的存储空间(创建的对象),手动申请的字节空间需要调用free来释放

3、Bss段:没有初始化的全局变量和静态变量,一旦初始化就会从BSS段中收回掉,转存到数据段中

4、数据段:存放已经初始化的全局变量和静态变量,以及常量数据,直到程序结束才会被立即收回

5、代码段:程序编译后的代码内容,直到结束程序才会被收回

iOS内存中的对象主要有两类,
一类是值类型,比如int、float、struct等基本数据类型
一类是引用类型,也就是继承NSObject类的所有OC对象
值类型会被放入栈中,遵循先进后出的原则
引用类型会放在堆中,当给对象分配内存空间时,对随机在内存中开辟空间。

为什么会循环引用

当两个不同的对象各有一个强引用指向对方,那么循环引用就产生了,每个对象的引用计数都会+1,无法得到内存的释放
weak是弱引用,计数器不会加一,并在引用对象被释放的时候自动设置为nil

unsafe_unretained , weak, assign 区别

  • __unsafe_unretained: 不会对对象进行retain,当对象销毁时,会依然指向之前的内存空间(野指针)
  • __weak: 不会对对象进行retain,当对象销毁时,会自动指向nil
  • assign:用于对基本数据类型进行赋值操作,不更改引用计数(基本类型内存分配栈上,系统自动处理)。如果来修饰对象,被assgin修饰的对象在释放后,指针的地址还是存在的,成为野指针。如果后续分配在堆上的内存正好在这个地址上程序就会crash。
  • copy:在修饰Mutable可变类型会在内存里拷贝一份对象,两个指针指向不同的内存地址。copy出来的新对象是不可变类型的。而修饰NSString、NSArray等普通类型,充当strong使用,内存计数+1
  • strong:强指针,指向对象内存地址,内存计数+1
  • nonatomic:非原子属性。它的特点是多线程并发访问性能高,但是访问不安全
  • atomic:原子性,线程安全的,setter方法加锁

野指针是什么,iOS 开发中什么情况下会有野指针?

指针是不为nil,但是指向已经被释放的内存的指针。
__unsafe_unretain或者assign的指针,对象释放后会出现野指针。
一般情况下oc使用了weak指针,在对象销毁时指针会置nil

block内存相关

问:__block什么时候用?
答:在block里面修改局部变量的值都要用__block修饰

问:在block里面, 对数组执行添加操作, 这个数组需要声明成__block吗?
答:不需要声明成__block,因为testArr数组的指针并没有变(往数组里面添加对象,指针是没变的,只是指针指向的内存里面的内容变了)

问:在block里面, 对NSInteger进行修改, 这个NSInteger是否需要声明成__blcok ?
答:NSInteger的值发生改变,则要求添加__block修饰

block变量定义时为什么用copy?block是放在哪里的?

默认情况下,block是存档在栈中,可能被随时回收,通过copy操作可以使其在堆中保留一份, 相当于一直强引用着, 因此如果block中用到self时, 需要将其弱化, 通过__weak或者__unsafe_unretained

autoreleasepool的使用场景和原理

自动释放池是OC中内存自动回收机制,它可以延迟加入autoreleasepool中变量release的时机,创建的变量会在超出其作用域的时候release

autorelease本质上就是延迟调用release

在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop(每一个线程都有一个默认autoreleasepool)

栈 - 泄露

int number = 4;
int *a = malloc(8);
a = &number;
free(a);

给指针a分配了8个字节的地址,a又指向了number的地址,最后a释放了。这时候释放的是number的地址,而number是在栈区中,不能被手动释放,这时候就出现了栈的内存泄露

weak、strong在内存的作用

默认情况下,一个指针都会使用__strong属性,表明这是一个强引用。当所有强引用都去除时,对象才能被释放

但有时候我们可能要禁止这种行为:一些集合类不应该增加其元素的引用,可能对导致对象无法释放,可能会出现循环引用,导致无法释放,这种情况下我们要使用弱引用__weak。

weak是弱引用,计数器不会加一,并在引用对象被释放的时候自动设置为nil

线程中栈与堆是公有的还是私有的

在多线程环境下,每个线程拥有一个栈和程序计数器,栈和程序计数器用来保存线程执行历史和线程执行的状态,是线程私有资源

堆资源是统一进程内多线程共享的

OC的内存是怎么管理的?

一般来说我们可能会说“使用引用计数管理”,是的没错,但是引用计数是如何管理的呢?

2013年9月,苹果推出了iPhone5s,推出TaggedPointer概念,为了节省内存和提高执行效率。
NSNumber,NSString,NSData等一些较小的数据TaggedPointer将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址

正常Object对象还是会使用引用计数来管理,那么苹果是这么管理的呢?

为了管理内存苹果内置了全局的SideTables(散列表),存储的是SideTable的结构体。

   SideTable 结构体
     struct SideTable{
        //保证原子操作的自旋锁
        spinlock_t slock;
        //引用计数的hash表
        RefcountMap refcnts;
        //weak 引用全局hash表
        weak_table_t weak_table;
    }

网上有个例子这里:

100个学生(对象)住宿,大家都在一个楼上(SideTables),有10个房间(SideTable),每个房间8个学生(obj);
关系类似于此

spinlock_t:自旋锁,如果已经在访问100号宿舍的某个学生,那么在这时候是其他线程是无法访问100号宿舍的其他学生,自旋锁比较适用于锁使用者保持锁时间比较短的情况

RefcountMap:对象具体的引用计数,没错就是他,所有strong等可以是引用计数+1的操作都会在这里标记,
因为一个SideTable可能有多个对象的计数器,SideTables[0x0000]和SideTables[0x0x000f] 可能都是同一个SideTable,所以苹果又提供了table.refcnts.find[0x0x000f]来找到真正的引用计数器
计数器的存储结构:

image

)
看图表示可以得知:真正引用计数器是从第三位开始,也就是4的位置。
如果这里引用计数为0了,就会直接执行dealloc,查看是否有weak引用会把weak_table_t中的弱引用置nil

weak_table_t:

 struct weak_table_t {
    // 保存了所有指向指定对象的 weak 指针(数组)
    weak_entry_t *weak_entries;
    // 存储空间
    size_t    num_entries;
    // 参与判断引用计数辅助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};

weak_entries:通过循环遍历来找到对应的entry。

struct weak_entry_t {
//被指对象的地址。前面循环遍历查找的时候就是判断目标地址是否和他相等
DisguisedPtrobjc_object> referent;
union {
    struct {
        //可变数组,里面保存着所有指向这个对象的弱引用的地址。当这个对象被释放的时候,referrers里的所有指针都会被设置成nil。
        weak_referrer_t *referrers;
        uintptr_t        out_of_line : 1;
        uintptr_t        num_refs : PTR_MINUS_1;
        uintptr_t        mask;
        uintptr_t        max_hash_displacement;
    };
    struct {
        //只有WEAK_INLINE_COUNT个元素的数组,默认情况下用它来存储弱引用的指针。当大于4个的时候使用referrers来存储指针。
        weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
    };
 }
}

weak的原理

为了管理所有的引用计数和weak指针,苹果创建了一个全局的SideTables,它是一个全局的hash表,用于存储指向某个对象的所有weak指针,key是所指向对象的地址,value是weak指针的地址数组,里面存放的都是SideTable结构体

weak是弱引用,计数器不会加一,并在引用对象被释放的时候自动设置为nil

对象引用计数相关的操作是原子性的,如果多个线程同事操作一个对象的引用计数会造成数据错乱,同时在内存中的对象数据量大,不能读整个Hash加锁,所以苹果采用了分离锁

步骤:
1、初始化时,runtime会调用obj_initWeak函数,初始化一个新的weak指针指向对象的地址

NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;

初始化weak变量的时候,runtime会调用NSObject.mm中的obj_initWeak函数,函数声明如下:

id objc_initWeak(id *object, id value);

而对于objc_initweak()方法的实现

id objc_initWeak(id *location, id newObj) {
// 查看对象实例是否有效
// 无效对象直接导致指针释放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 这里传递了三个 bool 数值
    // 使用 template 进行常量参数传递是为了优化性能
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}

2、添加引用时,obj_initWeak函数会调用obj_storeWeak()函数更新指针指向,创建对应的弱引用表

obj_storeWeak()函数声明

id objc_storeWeak(id *location, id value);

代码具体看:
http://www.cocoachina.com/ios/20170328/18962.html
https://www.desgard.com/weak/

3、释放时,调用clearDeallocating函数,首先根据对象地址获取到所有的weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录
http://www.cocoachina.com/ios/20170328/18962.html

相关链接:
http://sindrilin.com/runtime/2016/12/23/闲聊内存管理
http://zhoulingyu.com/2017/02/15/Advanced-iOS-Study-objc-Memory-2/
https://www.aliyun.com/jiaocheng/topic_39068.html
http://www.cocoachina.com/ios/20150605/11990.html
http://ios.jobbole.com/89012/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,357评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • OC语言基础 1.类与对象 类方法 OC的类方法只有2种:静态方法和实例方法两种 在OC中,只要方法声明在@int...
    奇异果好补阅读 4,265评论 0 11
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,960评论 1 16
  • 随着中国社会政治经济的快速发展,越来越多的青壮年农民走入城市,在广大农村也随之产生了一个特殊的未成年人群体——农村...
    南瓜君吖阅读 327评论 0 10