关于内存管理的总结

每一种编程语言在使用的过程中,都会开辟内存空间,常用的两种存储结构是栈区和堆区。这两种结构的使用特点和分配方式各有差异(在前面图吧导航引擎组的电面总结博客里已经浅析了他们的区别),内存管理所说的管理,大是管理的堆区,因为堆是由程序员自己申请空间大小,自己手动释放,可操作性比较强;而栈区是由系统自动分配,程序结束后自动释放内存,程序员无法控制。

我所接触到的内存管理的方式有以下几种:MRC、ARC、GC、手动管理(malloc\free、new\delete)、虚拟内存;

在iOS开发过程中,OC使用的是MRC\ARC,Swift使用的是ARC;
Java使用的是GC(垃圾回收机制);
C/C++使用的是手动管理的方式,C:malloc\free;C++:new\delete
计算机管理物理内存使用的是设置虚拟内存的方法;

展开来讲:

为什么要进行内存管理?

由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等。

管理范围:任何继承NSObject的对象,对其他的基本数据类型无效。

本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄露。

IOS中的内存管理:

MRC(MannulReference Counting):手动引用计数。

是在我们申请到某一块内存,在使用之后,要手动释放,释放机理涉及到计数器问题,在创建了一个对象之后,计数器自动设为1,如果要调用这个对象,要做retain操作,使计数器+1,使用完成后要做release,使计数器-1,当retainCount 的值为0事,释放该对象所占的内存。如果未释放内存,会造成内存的浪费,俗称内存泄露,

MRC的工作原理(工作过程):在创建对象时,使用alloc方法使引用计数器(retainCount )为1,调用该对象时,要做retain操作,使引用计数器+1,使用完该对象后,要做release操作,使引用计数器-1,最后当引用计数器(retainCount )为0时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,调用[super dealloc],在此处释放内存。

与对变量的管理相关的主要方法有:retain,release,autorelease;

1.和内存管理相关的方法
1)alloc 引用计数器自动设为1
2)retain 引用计数器+1 返回了经过+1以后的当前实例对象
3)release 引用计数器-1,并不一定是释放
4)retainCount 获取引用计数器的值,当值为0时,那么该对象将被销毁,其占用的内存被系统回收。
5)dealloc 当实例对象被销毁之前,系统自动调用。
一定要调[super dealloc]
6)Autorelease 将对象放入一个自动释放池中,当自动释放池被销毁时,会对池中所有对象做一次release。占用内存较大的对象不宜用此方法。

示例代码:

//假设Number为预定义的类

Number* num = [[Number alloc] init];

Number* num2 = [num retain];//此时引用记数+1,现为2

[num2 release]; //num2 释放对内存数据的所有权 引用记数-1,现为1;

[num release];//num释放对内存数据的所有权 引用记数-1,现为0;

[num add:1 and 2];//bug,此时内存已释放。

当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关的资源,dealloc就像是对象的“临终遗言”。一旦重写了dealloc方法就必须调用[super dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。

dealloc方法的代码规范

(1)一定要[super dealloc],而且要放到最后

(2)对self(当前)所拥有的的其他对象做一次release操作

-(void)dealloc

{

[_car release];

[super dealloc];

}

//autoreleasepool 的使用 在MRC管理模式下,我们摒弃以前的用法,NSAutoreleasePool对象的使用,新手段为@autoreleasepool

@autoreleasepool {

       Number* num = [[Number alloc] init];

              [numautorelease];//由autoreleasepool来管理其内存的释放

   }

对与Objective-c中属性的标识符可以总结为:

@property (nonatomic/atomic,retain/assign/copy, readonly/readwrite) Number* num;

(1) nonatomic/atomic,表示该属性是否是对多线程安全的,是不是使用线程锁,默认为atomic,

(2) retain/assign/copy,是有关对该属性的内存管理的,

和内存管理相关的名词
1)僵尸对象:此对象被销毁,不能再使用,不能给它发送任何消息
2)野指针:指向僵尸对象(不可用的内存)的指针,给野指针发送消息将会产生不可控的后果。
3)空指针:没有指向任何对象的指针,给空指针发消息不会产生任何行为

内存管理原则

(一)原则

只要还有人在使用某个对象,那么这个对象就不会被回收;

只要你想使用这个对象,那么就应该让这个对象的引用计数器+1;

当你不想使用这个对象时,应该让对象的引用计数器-1;

(二)谁创建,谁release

(1)如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者autorelease方法

(2)不是你创建的就不用你去负责

(三)谁retain,谁release

只要你调用了retain,无论这个对象时如何生成的,你都要调用release

(四)总结

有始有终,有加就应该有减。曾经让某个对象计数器加1,就应该让其在最后-1.

ARC(AutomaticReference Counting):自动引用计数;

ARC的工作原理还是MRC的工作原理,只不过是原先需要手动处理内存管理的引用计数器的代码全部由编译器来替我们完成。

ARC使得开发者在使用alloc申请了一块内存之后,其他的都交给运行器自动管理,开发者不需要再考虑何时使用retain、release、autorelease这样的函数来管理内存,它提供了自动评估内存生存期的功能,并且在编译器中自动加入合适的管理内存的方法。编译器也会自动生成dealloc函数。对程序员来说平时工作强推ARC,但是了解MRC的工作原理也是十分必要的。

在ARC中与内存管理有关的标识符,可以分为变量标识符和属性标识符,对于变量默认为__strong,而对于属性默认为unsafe_unretained。也存在autoreleasepool。

对于变量的标识符有:

(1) __strong,is the default. An object remains “alive” as long as there is a strong pointerto it.

(2) __weak,specifies a reference that does not keep the referenced object alive. A weakreference is set to nil when there are no strong references to the object.

(3)__unsafe_unretained,specifies a reference that does not keep the referenced object alive and is notset to nil when there are no strong references to the object. If the object itreferences is deallocated, the pointer is left dangling.

(4)__autoreleasing,is used to denote arguments that are passed by reference (id *) and areautoreleased on return,managedby Autoreleasepool.

对于变量标识符的用法:

__strong Number* num = [[Number alloc]init];

在ARC内存管理模式下,其属性的标识符存在以下几种:

@property (nonatomic/atomic, assign/retain/strong/weak/unsafe_unretained/copy,readonly/readwrite) Number* num;//默认为unsafe_unretained

其中assign/retain/copy与MRC下property的标识符意义相同,strong类似与retain,assign类似于unsafe_unretained,strong/weak/unsafe_unretained与ARC下变量标识符意义相同,只是一个用于属性的标识,一个用于变量的标识(带两个下划短线__)。所列出的其他的标识符与MRC下意义相同。

(1)对于assign,你可以对标量类型(如int)使用这个属性。你可以想象一个float,它不是一个对象,所以它不能retain、copy。

(2)对于copy,指定应该使用对象的副本(深度复制),前一个值发送一条release消息。基本上像retain,但是没有增加引用计数,是分配一块新的内存来放置它。特别适用于NSString,如果你不想改变现有的,就用这个,因为NSMutableString,也是NSString。

ARC的特点总结:

(1)不允许调用release,retain,retainCount ,autorelease

(2)不允许重写dealloc,但是不允许调用[super dealloc]

(3)@property的参数:

Strong:相当于原来的retain(适用于OC对象类型),成员变量是强指针

Weak:相当于原来的assign,(适用于oc对象类型),成员变量是弱指针

Assign:适用于非OC对象类型(基础类型)

4)不可以使用NSAllocateObject\NSDeallocateObject\NSAutoreleasePoll

5)作为替代,@autoreleasepool被引入,可以使用这个效率更高的关键字;

6)当使用alloc申请了一块内存之后,其他的都交给运行器自动管理了。

使用ARC可能出现的循环引用问题:

所有的自动引用计数机制都有一个从理论上无法绕过的限制,那就是循环引用 (retain cycle) 的情况。

这里写图片描述

object1和object2之间形成了循环引用,它们的引用计数始终为1,始终不会被释放,这就造成了内存泄漏。

示例代码:

假设我们有两个类 A 和 B , 它们之中分别有一个存储属性持有对方:

class A {
  let b: B
  init() {
    b = B()
    b.a = self
  }
  deinit {
    println("A deinit")
  }
}
class B {
  var a: A? = nil
  deinit {
    println("B deinit")
  }
}

在 A 的初始化方法中,我们生成了一个 B 的实例并将其存储在属性中。然后我们又将 A 的实例赋值给了 b.a 。这样 a.b 和 b.a 将在初始化的时候形成一个引用循环。现在当有第三方的调用初始化了 A ,然后即使立即将其释放, A 和 B 两个类实例的 deinit 方法也不会被调用,说明它们并没有被释放。

func application(application: UIApplication!, 
         didFinishLaunchingWithOptions launchOptions: NSDictionary!) 
         -> Bool 
{
  // Override point for customization after application launch.
  var obj: A? = A()
  obj = nil
  // 内存没有释放
  return true
}

因为即使 obj 不再持有 A 的这个对象,b 中的 b.a 依然引用着这个对象,导致它无法释放。而进一步,a 中也持有着 b,导致 b 也无法释放。在将 obj 设为 nil 之后,我们在代码里再也拿不到对于这个对象的引用了,所以除非是杀掉整个进程,我们已经 永远 也无法将它释放了。多么悲伤的故事啊..

**

循环引用解决办法:

**

为了防止这种人神共愤的悲剧的发生,我们必须给编译器一点提示,表明我们不希望它们互相持有。一般来说我们习惯希望 “被动” 的一方不要去持有 “主动” 的一方。在这里 b.a 里对 A 的实例的持有是由 A 的方法设定的,我们在之后直接使用的也是 A 的实例,因此认为 b 是被动的一方。可以将上面的 class B 的声明改为:

class B {
    weak var a: A? = nil
    deinit {
        println("B deinit")
    }
}

在 var a 前面加上了 weak ,向编译器说明我们不希望持有 a。这时,当 obj 指向 nil 时,整个环境中就没有对 A 的这个实例的持有了,于是这个实例可以得到释放。接着,这个被释放的实例上对 b 的引用 a.b 也随着这次释放结束了作用域,所以 b 的引用也将归零,得到释放。添加 weak 后的输出:

A deinit
B deinit

以上所讲的是OC和Swift中使用的两种内存管理办法ARC和MRC,剩下的两种内存管理办法,C和C++的内存管理和虚拟内存的相关知识在我前面的一篇图吧导航引擎组电面总结的博客里有详细说明,有兴趣的朋友可以翻阅查看,如有不足之处,望留言批评指正。

发布了38 篇原创文章 · 获赞 15 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章