内存管理之-----alloc retain release dealloc,autorelease的实现

1、Stack(栈)、Heap(堆)的概念

值类型是在stack上创建的,引用类型是在heap上创建的。那么stack和heap在内存上究竟是怎样的呢?

stack和heap保存在哪里?

stack和heap都是保存在随机存取存储器(RAM Random Access Memory),也就是我们平常所说的内存(条),RAM是与CPU直接交换数据的内部存储器,断电后会丢失信息,因为可读写,速度快,因此通常作为操作系统或程序运行时临时数据存放的地方。

我们知道,一个操作系统中的程序的运行活动就是进程,进程中可以包含若干线程(thread),一个标准的线程是由线程ID,当前指令指针、寄存器集合(CPU组成部分,因此,线程是消耗CPU资源)和堆栈(就是栈)组成的

在多线程的应用程序中,每个线程都有其自己的stack(堆栈),但是,多个不同线程是共享资源的(也就是共享heap),这也意味的多线程需要一定的调控来防止不会再同一时间内访问相同的heap空间。

因此:

  • stack是内存为线程分配的活动空间,stack读取速度相对heap更快,一般用于存储本地数据(如数值类型),对象指针(如创建对象后返回的地址)和函数参数。比如我们在A函数内又调用了B函数,执行A函数时,会先在栈顶分配一个A函数的block,里面保存局部变量与数据,当调用到B函数并执行其代码时,又会在A函数的block上(也就是栈顶)分配一个B函数的block。B函数执行完成并return后,B函数的block被释放,里面的变量和数据会被销毁,等A函数的代码执行完成后,其block又会被释放,里面的变量和数据也被销毁。stack总是按照先进后出的顺序(LIFO)保留数据。
  • 栈和堆的区别
  • 1.heap是堆,stack是栈。
    2.stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用new关键字来分配。
    3.stack空间有限,heap的空间是很大的自由区。在Java中,若只是声明一个对象,则先在栈内存中为其分配地址空间,若再new一下,实例化它,则在堆内存中为其分配地址。
    4.举例:数据类型 变量名;这样定义的东西在栈区。如:Object a =null; 只在栈内存中分配空间new 数据类型();或者malloc(长度); 这样定义的东西就在堆区如:Object b =new Object(); 则在堆内存中分配空间
  • 5、heap则是动态分配内存的空间,你必须手动创建内存空间并释放它。heap的读取速度比stack慢些,在stack中,操作变量都是对内存空间的直接操作(访问模式是基于指针的递增和递减,并且由于常用,会被映射到处理器的缓存中),而在heap,则是通过对象指针进行操作
2、GNUstep关于alloc、retain、release和dealloc的实现( GNUstep是Cocoa的互换框架)
对象操作与OC方法的对应,这些有关内存管理的方法,实际上不包括在该语言中,而是包括在Cocoa框架中用于OS X ,IOS的开发。Cocoa框架中Foundation框架类库的NSObject类担负内存管理的职责。
  生成并持有对象(类方法):alloc/new/copy/mutableCopy
持有对象(实例方法):retain
释放对象(实例方法):release
废气对象(实例方法):dealloc

调用NSObject的alloc方法,如下

id obj = [NSObject alloc];

在GNUstep的实现是:

+(id)alloc{ return [self allocWithZone:NSDefaultMallocZone()]; }

+(id)allocWithZone:(NSZone *)z { return NSAllocateObject (self, 0, z);}

//通过allocWithZone:类方法调用NSAllocateObject 函数分配了对象

NSAllocateObject()函数的实现

struct obj_layout{ NSUInteger retained;};

inline id

NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)

{

int size = /* needed size to store the object */
id new = NSZoneMalloc(zone, size);
memset(new, 0, size);//将new指针所指的内存区域的前size个字节设置成0,函数原型如下:

extern void *memset(void *buffer, int c, int count);
把buffer所指内存区域的前count个字节设置成字符c,并且返回指向buffer的指针
new = (id)&((struct obj_layout *)new)[1];

}NSAllocateObject()函数通过调用NSZoneMalloc函数来分配存放对象所需的内存空间,再将该空间置为0,最后返回作为对象而使用的指针

NSZone类,NSZone是最初用于防止内存碎片化的,通过把空间根据内存大小分成具有规则的块,可以使内存的使用效率提高,不过如今,ObjC的Runtime有更好的内存管理算法,因此,在Cocoa框架,这种使用NSZone来优化内存已被弃用。除去NSZone的相关代码,alloc方法可以重写为:

struct obj_layout{ NSUInteger retained; };//引用计数

+(id)alloc

{

int size = sizeof(struct obj_layout) + size_of_the_object;分配内存的大小还包括引用计数的空间

struct obj_layout *p = (struct obj_layout *)calloc(1,size);//不太理解

//calloc(1,size):创建一个大小为size的空间

//*p:把空间的首地址赋给obj_layout类型的指针便变量p

return (id)(p+1);//返回指针变量p只想下一个obj_layout类型元素的首地址

}

对象的引用计数retainCount的实现

id obj = [[NSObject alloc]  init];

NSLog(@"retainCount = %d",[obj retainCout]);

         其实现方法如下:      

- (NSUInteger)retainCout { return NSExtraRefCount(self)+1; }//使用alloc动态分配的内存块被初始化为0,即retainCount=0,实际上分配内存空间后,应为1,所以加1

inline NSUInteger

NSExtraRefCount(id anObject)

{

return ((struct obj_layout *)anObject)[-1].retained;//-1使得指针指向内存空间块的首地址

}

retain的实现

[obj retain];

-(id)retain { NSIncrementExtraRefCount(self); return self; }

inline void

NSIncrementExtraRefCount(id anObject)

{

if (((struct obj_layout *)anObject)[-1].retained == UINT_MAX - 1)
     [NSException raise: NSInternalInconsistencyException format: @"NSIncrementExtraRefCount() asked to increment too far"];


((struct obj_layout *)anObject)[-1].retained++;

}

       调用retain方法,实际上是让obj_layout结构体的变量retained自增。release方法呢?

[obj release];

- (void)release { if (NSDecrementExtraRefCoutWasZero(self) [self dealloc];};

Bool NSDecrementExtraRefCoutWasZero(id anObject)

{

if(((struct obj_layout *)anObject)[-1].retained == 0){

return YES;

}else{

(struct obj_layout*)anObject[-1].retained--;

return NO;

}

}

          调用release方法时,先会获取obj_layout结构体变量retained的值,若值为0,则返回YES并调用dealloc方法,                   否则,则对retained变量自减。

- (void)dealloc { NSDeallocateObject(self); }

inline void

NSDeallocateObject(id anObject)

{

struct obj_layout *o = &((struct obj_layout *)anObject)[-1];

free(o)

}

上面可以知道GNUstep中对alloc、retain、release的实现,通过这认识,我们可以猜测苹果Cocoa框架的实现方式

通过Xcode的调试器lldb,可以看到关于alloc的实现:

+alloc-->+allocWithZone-->class_createInstance -->calloc

alloc的实现过程除了class_createInstance,其他和GNUstep差不多,而class_createInstance则是ObjC的Runtime特性。

苹果是通过哈希表(reference counter table)来控制引用计数(不同于GNUstep),所有的引用计数都是保存在哈希表的词目上。

这种管理方式的最大优点是监控和管理内存,在出现内存问题是,通过哈希表有助于程序的调试。

GNUstep将引用计数保存在对象占用内存块头部的变量中,而苹果的实现则是保存在引用计数表的记录中。GNUstep和苹果各自的优点

GNUstep:

少量代码即可完成

能够统一管理引用计数用内存块和对象用内存块

苹果:

对象用内存的分配无需考虑内存块头部

引用计数表各记录中存有内存块地址,可从各个记录追溯到个对象的内存块

Autorelease内部的实现

demo:

NSAutoreleasePool * pool=[[NSAutoreleasePool alloc]init];

id obj=[[NSObject alloc]init];

[obj autorelease];

[pool drain];


[obj autorelease]实现如下:

-(id)autorelease

{

[NSAutoreleasePool   addObject:self];

}autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject方法

GNUstep中的autorelease实际上是用一种特殊的方法实现的,在进行方法调用时,为了解决雷鸣,方法名以及取得方法运行时的函数指针,要在框架初始化时对其结果值进行缓存

NSAutoreleasePool的实现

+(void) addObject:(id) anObj

{

NSAutoreleasePool * pool=取得正在使用NSAutoreleasePool对象

if(pool!=nil)   [pool  addObject:anObj];

else .......

}

addObject:类方法调用正在使用的NSAutoreleasePool对象的addObject方法

-(void) addObject:(id) anObj

{ [array addObject:anObj];}实际的GNUstep实现使用的是连接列表,这同在可变数组中追加对象参数是一样的。如果调用NSObject类的autorelease实例方法,该对象呗追加到正在使用的NSAutoreleasePool对象的数组里

[pool drain];的实现

-(void) drain

{[self dealloc];}

-(void)dealloc

{[self emptyPool];  [array release];}

-(void) emptyPool

{for(id obj in array)  [obj release];}

如果嵌套生成NSAutoreleasePool对象,理所当然会使用最内侧的对象

问题:如果autorelease  NSAutoreleasePool对象会如何?  会发生异常

原因:通常在使用OC,即Foundation框架时,无论调用哪个对象的autorelease实例方法,实际上调用的事NSObject类的autorelease实例方法,但是对于NSAutoreleasePool类,autorelease实例方法已被重载,因此运行时就会出错。













你可能感兴趣的:(ios,cocoa,OS,内存,内存管理)