浅谈内存管理及僵尸对象

简介: 浅谈内存管理及僵尸对象

//--------------------内存管理

内存管理范围:

管理任何继承NSObject的对象,基本数据类型不需要内存管理。

对象类型是程序运行过程中动态分配的,存储在堆区,内存管理主要是对"堆区中的对象"进行内存管理。

//--------------------概念

1)对象的所有权及引用计数

任何对象都可能拥有一个或多个所有者。只要一个对象至少还拥有一个所有者,它就会继续存在

2)对象的引用计数器

每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁。

在每个OC对象内部,都专门有8个字节的存储空间来存储引用计数器。

3)引用计数器的作用

判断对象要不要回收的唯一依据

(存在一种例外:对象值为nil时,引用计数为0,但不回收空间)就是计数器是否为0,若不为0则存在。

//----------------------对引用计数器的操作3个方法

给对象发送消息,进行相应的计数器操作。

retain消息:使计数器+1

release消息:使计数器-1(并不代表释放对象)

retainCount消息:获得对象当前的引用计数器值rc

//--------------------------对象的销毁

当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收。

//1.对象销毁就自动执行dealloc方法

当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这里释放相关的资源,dealloc就像是对象的"临终遗言"。

//2.重写dealloc方法

一旦重写了dealloc方法就必须调用[superdealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。

//3.对象销毁了存储空间不可用

一旦对象被回收了,那么他所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指针错误)。

#注意:

1)如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出)

2)任何一个对象,刚生下来的时候,引用计数器都为1。(对象一旦创建好,默认引用计数器就是1  )

3)当使用"alloc"、"new"或者"copy"("mutablecopy")创建一个对象时,对象的引用计数器默认就是1 ;

对应的方法:

-retain         +1

-release        -1

-retainCount当前引用技数为多少0 --->系统释放这个对象

-dealloc

#warning开放如何使用:需要理解MRC,但实际使用时尽量用ARC

//------------------------MRC

target   Build Settings   Basic   Leveis搜索auto-->NO

内存管理的关键是"如何判断对象被回收"

问题:

1.对象创建完之后默认引用计数是多少?      1

2.当对象的引用计数为多少?     0系统就会回收这个对象的空间

3.怎么知道这个对象空间被释放了?

重写dealloc方法在dealloc方法中打印以下信息

-------------------关于dealloc方法

代码规范:

(1)一定要[superdealloc],而且要放到最后,意义是:"先释放子类占用的空间再释放父类占用的空间"

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

-(void)dealloc

{

//先释放子类拥有的对象属性

[superdealloc];

}

注意:

永远不要直接通过对象调用dealloc方法(对象的引用计数为0系统会自动调用此方法)

-------------------------------------

原则

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

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

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

记住两点:

1,谁创建"alloc","new",谁"release";

2,谁"retain",谁"release";

正确的手动管理操作:

一个alloc/new对应一个release

一个retain对应一个release

总结

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

//---------------------僵尸对象与内存泄露

区分以下2个概念:

1)"僵尸对象"(野指针)(没有初始化的指针变量;指向的内存空间已经被释放的指针变量)

栈区,系统会自动管理

//空指针:没有指向任何东西的指针,给空指针发送消息不会报错对象= nil

2)"内存泄露"(栈区的指向已经释放,堆区的空间没有释放,这时堆区的空间就被泄露了)

堆区,需要程序员手动管理

#不管是多个对象还是单个对象,只要我们研究它的内存管理,就两点:

#1.僵尸对象(野指针)

#2.内存泄露

//---------------------------关于僵尸对象问题

首先什么是僵尸对象:

"僵尸对象"(野指针)(没有初始化的指针变量;指向的内存空间已经被释放的指针变量)

栈区,系统会自动管理

#检测野指针

(检测僵尸对象,比较耗费性能,所以Xcode默认是不检测的)

打开方法:

Edit Scheme  -->  Run Debug   --->  Diagnostics  -->  Enable Zombie Objects(打勾)

对象一旦成为了僵尸对象,就不能再使用这个僵尸对象了

#判断是否是僵尸对象:

person(对象)  -->看什么?

看是否执行了dealloc方法--->重写dealloc方法打印一句话提醒

dealloc方法一旦调用了,意味着堆区的空间已经被释放,释放就意味着这个对象是僵尸对象

#避免使用僵尸对象的方法

1)僵尸对象调用方法,会报错,访问成员变量有时是可以的(但是这个是未知的)。

2)为了防止不小心调用了僵尸对象,可以将对象赋值nil(对象的空值)

用nil调用方法是不会报错的。

#关键:

3)总而言之要合理使用release和retain

//-----------------nil和Nil及NULL、NSNull的区别:

了解:

1.nil对象的值person =nil;

2.Nil类对象的值

3.NULLC语言的关键字通用空指针

4.  NSNull空对象用在不能使用nil的地方

//---------------------------关于内存泄露问题

首先:

什么是内存泄露:

"内存泄露"(栈区的指向已经释放,堆区的空间没有释放,这时堆区的空间就被泄露了)

堆区,需要程序员手动管理

#对象的内存泄露的几种情况

1) retain和release不匹配,retain多余release导致的内存泄露;

2)对象使用过程中,没有被release,而被赋值为nil;

3)在方法中不当的使用了retain;

#关键:

3)总而言之要合理使用release和retain

还是记住两点:

1.谁创建"alloc","new",谁"release";

2.谁"retain",谁"release";

内存管理的验证:

1.查看retainCount的值

2.重写dealloc方法,查看对象是否调用了dealloc方法

/*

一个alloc/new对应一个release

一个retain对应一个release

*/

//-----------------------多对象的内存管理

#多个对象的僵尸对象问题

假设对象A与对象B有(关联)关系,如果对象B独自销毁,会影响对象A的操作.

{

Car *_car;

}

假设:person拥有车

{

//创建一个人对象rc = 1

Person *person = [Person new];

//创建一个车对象rc = 1

Car *car = [Car new];

//让人拥有这辆车

person.car = car;

//人调用开车这个方法

[person driver];

[car release];

#关键的地方在这里看这里!!

#    [person driver]不能再调用这个方法人是拥有车的,人都还没释放,就不能调用与car有关系的driver方法是不科学的

[person release];

}

#解决方案

1.在人的实例变量_car的set方法中set方法中[_car retain];     +1

2.在人的dealloc方法中先把自己拥有的对象先释放[_car release];  -1

总结:

凡事关联关系,对象A与对象B有(关联)关系,就应该在set方法中让关联的对象+1,然后在对象A的dealloc方法中让关联的对象-1.

//------------------------------------------------

#多个对象的内存泄露问题

问题:

就上面的解决方法,还是有弊端,会造成原对象泄露

比如说人中途换车了多次调用了关于_car的set方法导致_car多次retain而没有相对应的release方法,导致rc没变为0,对象没有释放,就说内存泄露了

1.人第"1"次使用set方法赋值第一次把byd赋值给人的_car

2.人第"2"次使用set方法赋值第二次把byd赋值给人的_car

3.人第"3"次使用set方法赋值第三次把bigben赋值给人的_car

1.byd(首次赋值)  _car =nil_car != bydif执行[_car release]由于_car是nil[nilrelease]赋值赋值之后_car retain    byd+1

2.byd(多次调用)  _car = byd  _car == bydif不执行

3.bigBen(换车)  _car = byd  car = bigBen  _car != carif执行[_car release] --> [byd release]赋值赋值指针bigBen retain   bigBen + 1

在dealloc方法中:

如果只执行了第1步或者第1第2步则:

[_car release];  _car (byd) release    -1

如果执行了第1,2,3步则:

[_car release];//  _car  ---> bigBen     -1

-(void)setCar:(Car *)car{

if(_car != car){//  _car != car           _car实例变量!=    car参数

[_car release];//先release旧值

_car = car;//赋新值_car = [car retain];

[_car retain];//再retain新值

}

#判断是否是同一个对象,release旧值,retain新值

}

-(void)dealloc{

//该对象在被释放之前,必须先把自己所拥有的对象释放,也就是release一次自己的实例变量(对象)

[_car release];

[superdealloc];

}

//----------------------------set方法内存管理

#基本数据类型: int float double long struct enum基本数据类型做实例变量,就正常写法

-(void)setAge:(int)age{

_age = age;

}

#对象类型:对于对象作为另一个类的实例变量

判断是否是同一个对象,release旧值,retain新值

-(void)setCar:(Car *)car{

if(_car != car){

[_car release];//先release旧值

_car = car;//赋新值

[_car retain];//再retain新值

}

}

//------------------------@property参数

@property帮我们生成get和set方法的声明和实现

#格式: @property (参数1,参数2)数据类型实例变量名(记住:此处不要带下划线)

#原子性:   (习惯不加锁)

"atomic":默认值,对属性加锁,多线程下线程安全

"nonatomic":对属性不加锁,多线程下不安全,但速度快

#读写属性: (用得不多)

"readwrite":默认值,生成getter,setter方法.

"readonly":只生成getter方法

#  setter方法的处理

1)基本数据类型或者C语言的构造类型:直接赋值

intfloatdoublelongstructenum等BOOL/ Boolean

"assign":@property的默认值,直接赋值

//如果使用assign  set方法就会生成如下代码

-(void)setAge:(int)age{

_age = age;

}

2)OC对象类型

"retain":先release原来的值,再retain新值

"copy":先release原来的值,再copy新值(NSString *)

//如果使用retain  set方法就会生成如下代码

-(void)setCar:(Car *)car{

if(_car != car){

[_car release];

_car = [car retain];

}

}

练习

{

Car *_car;

int_age;

enumSex _sex;

Dog *_dog;

NSString *_name;

}

@property(retain,nonatomic) Car *car;

@property(nonatomic,assign)intage;

@property(nonatomic,assign)enumSex sex;

@property(nonatomic,retain) Dog *dog;

@property(nonatomic,copy) NSString *name;

@property(nonatomic,assign,setter= setIsVip:,getter= haha)BOOLvip;

原方法名

-(void)setVip:(BOOL)vip;

-(BOOL)vip;

setter = setIsVip:,getter = haha

// setter=setIsVip:

// getter=haha方法名改了

如果使用中括号调用方法,要必须把方法名写正确了

如果使用点语法,就可以.实例变量名


相关文章
|
26天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
51 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
26天前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
30 2
|
26天前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
48 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
28 0
深入理解java对象的内存布局
|
1月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(二)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
28天前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。
|
1月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(三)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
1月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(一)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
3月前
|
存储 Java 程序员
Java中对象几种类型的内存分配(JVM对象储存机制)
Java中对象几种类型的内存分配(JVM对象储存机制)
79 5
Java中对象几种类型的内存分配(JVM对象储存机制)
|
3月前
|
存储 程序员 Python
Python类的定义_类和对象的关系_对象的内存模型
通过类的定义来创建对象,我们可以应用面向对象编程(OOP)的原则,例如封装、继承和多态,这些原则帮助程序员构建可复用的代码和模块化的系统。Python语言支持这样的OOP特性,使其成为强大而灵活的编程语言,适用于各种软件开发项目。
32 1