美文网首页
内存管理

内存管理

作者: pixie星星啊 | 来源:发表于2019-01-16 17:35 被阅读0次

内存管理

内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其主要目的是如何高效、快速的分配。

大纲

  1. 堆和栈
  2. 引用计数
  • MRC
    深浅拷贝
    autorelease
  • ARC
    所有权修饰符
    常见的几种内存泄漏
    使用instruments检测内存泄漏

堆和栈

一种特别的树状的数据结构。
在队列中,调度程序反复提取队列中的第一个作业并运行,因为实际情况中某些较短的任务要等待很长时间才能结束,或某些不短小,但具有重要性的作业,同样具有优先权。堆即为解决此类问题设计的一种数据结构。(按照元素的优先级取出元素)

一种特殊的串列形式的抽象数据结构,其特殊之处在于只能允许在链表或数组的一端进行加入数据或输出数据的运算。
后进先出LIFO。

内存分配中的堆和栈

内存分布结构.png

堆栈空间分配

栈,是一块连续的内存区域,从高地址往低地址扩展。由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆,是一块不连续的内存区域,从低地址往低高址扩展。一般由程序员释放,若程序员不释放,程序结束后可能由OS释放,分配方式类似于链表。
堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配,动态分配是alloca函数进行分配。栈的动态分配和堆的动态分配不同,是由编译器释放的。

堆栈缓存方式

栈,一级缓存,被调用时处于存储空间中,调用完毕立即释放。
堆,二级缓存,生命周期由虚拟机的垃圾回收算法来决定。调用速度相当对慢

引用计数

概念

一种内存管理技术,将资源的被引用计数保存起来,当引用计数变为零时就将其释放的过程。

MRC(Manual Reference Counting)

自己生成的对象,自己持有。(使用alloc,new,copy,mutablecopy或以这些单词开头的方法)
非自己生成的对象自己也能持有。(retain,非自己生成的对象内部一般是用的autorelease实现的,可以做到本身不持有对象例如:[NSArray array]操作)
不再需要自己持有的对象时释放。(release)
非自己持有的对象不能释放。(倘若在程序中释放了非自己所持有的对象就会造成崩溃)

深浅拷贝

copy,mutablecopy。一般系统自带的类实现了NSCopying和NSMutableCopying协议,自定义的类需要自己实现NSCopying和NSMutableCopying协议。

  • 浅拷贝 指针拷贝,内存地址相同
  • 深拷贝 值拷贝,内存地址不同
NSMutableArray NSArray NSString NSMutableString
copy 深拷贝 浅拷贝 浅拷贝 深拷贝
mutableCopy 深拷贝 深拷贝 深拷贝 深拷贝

总结:可变对象不论执行copy操作还是mutablecopy操作,都是深拷贝,不可变对象执行mutablecopy操作是深拷贝。深拷贝的数组里的元素是浅拷贝(内存地址相同)

autorelease

当对一个对象发送autorelease消息,会将对象添加到NSAutoreleasePool中去,当自动释放池执行drain操作时,会自动给里面的对象发送release消息,来释放对象。

ARC(Automatic Reference Counting)

编译器在编译时会帮我们自动插入
retain,release,copy,autorelease,autoreleasepool。
-fno-objc-arc,可以指定某个文件不使用ARC.

autoreleasepool

autorelease对象到底是何时被析构的?
一个常见的误区就是代码块执行完毕会释放,但其实并不是这样的。autoreleasepool的drain操作是跟runloop有关的,一个runloop结束以后才会执行drain操作,所以在代码块执行完毕后autorelease对象不一定会会析构。

for (int i =0;i<1000000;i++){
NSString *str = [NSString stringWithString:@"hahahha"];
}
for (int i =0;i<1000000;i++){
    @autoreleasepool{
    NSString *str = [NSString stringWithString:@"hahahha"];
    }
}
  • 结论:
    第二段代码会对内存进行优化,释放速度块
    第一段代码造成内存大量堆积,释放速度慢
    使用场景:当程序有大量中间临时变量产生时,避免内存峰值过高,及时释放内存的场景

所有权修饰符

  • __strong

__strong修饰符表示对象的“强引用”,是id类型和对象类型默认的所有权修饰符。__strong修饰的变量在超出其作用域时,会释放其被赋予的对象。

  • __weak
    __weak弱引用,不能持有对象实例,当该对象被废弃时,此弱引用会自动失效且处于nil赋值的状态。
  • __unsafe_unretained
    __unsafe_unretained是不安全的修饰符,有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象,不持有对象。
  • __autoreleasing
    不用显示的附加__autoreleasing修饰符,这是由于编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool.
属性与所有权修饰符的对应关系
属性的修饰符 所有权修饰符
assign __unsafe_unretained
copy __strong
retain __strong
strong __strong
unsafe_unretained __unsafe_unretained
weak __weak

给属性赋值是就相当于赋值给附加各属性对应的所有权修饰符的变量中。只有copy不是简单的赋值操作,是通过NSCoping接口中的copyWithZone:方法赋值源所生成的对象。

常见的几种内存泄漏

内存泄漏就是当废弃的对象在超出其生命周期后继续存在。

  1. 对象类型变量作为C语言结构体成员
struct Data{
NSMutableArray __unsafe_unretained *array
}

注意,这里要加上__unsafe_unretained修饰符,否则会报错,对象类型的变量不能直接作为结构体的成员变量。(可以用__bridge和c语言变量进行值转换)

  1. 循环引用
  • 两个对象相互持有对方的强引用,可以用弱引用(weak)解决。
  • block持有self对象,需要在block块外面设置弱引用,里面设置强引用解决。
__weak __typeof(self) weakSelf = self;
self.block = ^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething];
};

这里block里面用强引用,为了保证在block里面访问self时能保证self不被释放。

  • NSTimer
    示例代码
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(onTimerTimeOut) userInfo:nil repeats:YES];
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
}
image.png

当前类被timer强引用,dealloc方法并不会执行。
有一种解决办法是像blockskit一样,用block实现,解除循环引用(不过也需要注意block的循环引用)

+ (id)bk_scheduledTimerWithTimeInterval:(NSTimeInterval)inTimeInterval block:(void (^)(NSTimer *timer))block repeats:(BOOL)inRepeats
{
    NSParameterAssert(block != nil);
    return [self scheduledTimerWithTimeInterval:inTimeInterval target:self selector:@selector(bk_executeBlockFromTimer:) userInfo:[block copy] repeats:inRepeats];
}

还有一种方法,利用中间类,来破除循环引用,这里就不多说啦。

使用instruments检测内存泄漏

有几种方式打卡instruments调试工具
1.Xcode->Open Develop Tool->Instruments
2.Product->Profile
3.长按运行按钮->Profile

一. 使用Leaks检测

  • 强引用
    无循环引用:


    15483201379515.jpg

    有循环引用:


    15483175101215.jpg
  • block循环引用
    无循环引用:


    15483202064026.jpg

有循环引用:


15483176662501.jpg
  • NSTimer
    无循环引用:


    15483204816830.jpg

    有循环引用:


    15483177310618.jpg

通过上面的图可以分析出来,使用Leaks只能检测出block的循环引用(红色x标记)。但其实以上几种情况发生循环引用时的内存都是只升不减的。

具体的查看方法
15483210740766.jpg

选中红色✘这个范围,在下方选择Call Tree,并勾选invert call tree和Hide system Library就可以看到具体发生内存泄漏调用的函数,双击则会定位到具体的代码。如下图:


15483212847970.jpg

Leaks还可以选择Cycles&Roots查看发生循环引用的地方


15483215773045.jpg

二.使用Allocation检测

15483218051451.jpg
对AViewController不断地进行push、pop操作,用Allocations可以查看到AViewController的Totol数不为0,然后就可以针对这个类的使用情况再深入排查可能会出现内存泄漏的地方
15483219394069.jpg
总结:虽然使用ARC后能够大大避免内存泄漏的出现,但还是有一些场景会导致内存泄漏,上述例子的场景还是比较容易避免的,在调用链长的时候一些循环引用就比较难发现了,因此我们在开发完一个功能模块后使用Instruments来检测一遍是比较好的习惯。
最后附上Demo
TestInstruments

相关文章

  • iOS内存管理详解

    目录 block内存管理 autorelease内存管理 weak对象内存管理 NSString内存管理 new、...

  • 第10章 内存管理和文件操作

    1 内存管理 1.1 内存管理基础 标准内存管理函数堆管理函数虚拟内存管理函数内存映射文件函数 GlobalMem...

  • 操作系统之内存管理

    内存管理 包括内存管理和虚拟内存管理 内存管理包括内存管理概念、交换与覆盖、连续分配管理方式和非连续分配管理方式(...

  • JavaScript —— 内存管理及垃圾回收

    目录 JavaScript内存管理内存为什么需要管理?内存管理概念JavaScript中的内存管理JavaScri...

  • OC - OC的内存管理机制

    导读 一、为什么要进行内存管理 二、内存管理机制 三、内存管理原则 四、MRC手动内存管理 五、ARC自动内存管理...

  • 3. 内存管理

    内存管理 内存管理包含: 物理内存管理; 虚拟内存管理; 两者的映射 除了内存管理模块, 其他都使用虚拟地址(包括...

  • Go语言——内存管理

    Go语言——内存管理 参考: 图解 TCMalloc Golang 内存管理 Go 内存管理 问题 内存碎片:避免...

  • jvm 基础第一节: jvm数据区

    程序内存管理分为手动内存管理和自动内存管理, 而java属于自动内存管理,因此jvm的职能之一就是程序内存管理 j...

  • 内存管理

    内存管理的重要性。 不进行内存管理和错误的内存管理会造成以下问题。 内存泄露 悬挂指针 OC内存模型 内存管理是通...

  • 11-AutoreleasePool实现原理上

    我们都知道iOS的内存管理分为手动内存管理(MRC)和自动内存管理(ARC),但是不管是手动内存管理还是自动内存管...

网友评论

      本文标题:内存管理

      本文链接:https://www.haomeiwen.com/subject/agamdqtx.html