内存管理

内存管理一直是一个不易处理的问题。开发人员必须考虑分配回收的方式和时机,针对堆和栈做不同的优化处理,等等。内存管理的核心是动态分配的对象必须保证在使用完成后有效地释放内存,即管理对象的生命周期。因为C++是一个较为底层的语言,其设计上不包括不论什么智能管理内存的机制。一个对象在使用完成后必须被回收。然而在复杂的程序中。对象全部权在不同程序片段间传递或共享,使得确定回收的时机十分困难,因此内存管理成为了程序猿十分头疼的问题。

还有一方面,过于零散的对象分配回收可能导致堆中的内存碎片化,减少内存的使用效率。因此,我们须要一个合适的机制来缓解这个问题。


Boost库引入的智能指针(以及C++11引入的共享指针)从对象全部权传递的角度来解决内存管理问题。可是。在非常多情况下,智能指针还是显得单薄而无力。由于实际开发中对象间的关系十分复杂。全部权传递的操作在开发过程中会变得冗杂不堪。于是,各种基于C++的第三方工具库和引擎往往都会实现自己的智能内存管理机制来解决内存管理的难题。试图将开发人员从烦琐而晦涩的内存管理中解放出来。

主流的内存管理技术

眼下,主要有两种实现智能管理内存的技术,一是引用计数。二是垃圾回收。


1)引用计数
它是一种非常有效的机制。通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数。

当对象添加一次引用时,计数器加1;而对象失去一次引用时,计数器减1。当引用计数为0时,标志着该对象的生命周期结束。自己主动触发对象的回收释放。引用计数的重要规则是每个程序片段必须负责任地维护引用计数,在须要维持对象生存的程序段的開始和结束分别添加和降低一次引用计数。这样我们就能够实现十分灵活的智能内存管理了。

实际上,C++中的std::shared_ptr内部就是通过引用计数实现。



2)垃圾回收
它通过引入一种自己主动的内存回收器。试图将程序猿从复杂的内存管理任务中全然解放出来。它会自己主动跟踪每个对象的全部引用。以便找到全部正在使用的对象,然后释放其余不再须要的对象。

垃圾回收器还能够压缩使用中的内存,以缩小堆所须要的工作空间。垃圾回收能够防止内存泄露。有效地使用可用内存。可是。垃圾回收器一般是作为一个单独的低级别的线程执行的。在不可预知的情况下对内存堆中已经死亡的或者长时间没有使用过的对象进行清除和回收,程序猿不能手动指派垃圾回收器回收某个对象。回收机制包含分代复制垃圾回收、标记垃圾回收和增量垃圾回收等,一般垃圾回收机制应用在受托管的语言中(即执行在虚拟机中),如C#、Java以及一些脚本语言。


Cocos2d-x的内存管理

cocos2d-x中使用的是上面的引用计数来管理内存,可是又添加了一些自己的特色(自己主动回收池)。

cocos2d-x中通过Ref类(在2.x中是CCObject)来实现引用计数,全部须要实现内存自己主动回收的类都应该继承自Ref类。

对象创建时引用计数为1,对象拷贝时引用计数加1。对象释放时引用计数减1。假设引用计数为0时释放对象内存。以下是Ref类的定义(为了简洁去掉了当中的断言语句。但不影响其代码完整性):

class CC_DLL Ref
{
public:
// 将引用计数加1
void retain()
{
++_referenceCount;
} // 将引用技术减1,假设引用计数为0。释放当前对象
void release()
{
--_referenceCount;
if (_referenceCount == 0)
{
delete this;
}
} // 将当前对象增加到当前的自己主动回收池中
Ref* autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
} unsigned int getReferenceCount() const
{
return _referenceCount;
} protected:
Ref()
: _referenceCount(1) // when the Ref is created, the reference count of it is 1
{} public:
virtual ~Ref(); protected:
/// count of references
unsigned int _referenceCount; friend class AutoreleasePool;
};
在cocos2d-x中创建对象通常有两种方式:
1)auto sprite = new Sprite;
2)auto sprite = Sprite::create();
Object *Object::create()
{
Object *obj = new Object;
if (obj && obj->init())
{
obj->autorelease();
}
else
{
delete obj;
obj = nullptr;
}
return obj; }
这两中方式的差异能够參见我还有一篇博文“【cocos2d-x 3.x 学习笔记 02】对象创建方式讨论”。在cocos2d-x中提倡使用另外一种方式,为了避免误用第一种方式。一般将构造函数设为 protected 或 private。


使用静态工厂方式 create() 创建对象,对象创建成功后会调用对象的autorelease()方法将对象增加到当前自己主动回收池中。场景一帧绘制结束时会遍历当前自己主动回收池中的全部对象运行其release()方法。然后清空自己主动回收池中的对象。通过以下的函数调用顺序能够找到一帧绘制结束时,清理自己主动回收池的地方:
// main.cpp
return Application::getInstance()->run(); // Appdelegate.cpp
int Application::run()
{
...
while(!glview->windowShouldClose())
{
QueryPerformanceCounter(&nNow);
if (nNow.QuadPart - nLast.QuadPart > _animationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart;
// Enter loop
director->mainLoop();
glview->pollEvents();
}
else
{
Sleep(0);
}
}
...
} // CCDirector.cpp
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (! _invalid)
{
drawScene(); // release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
} // CCAutoReleasePool.cpp
void AutoreleasePool::clear()
{
for (const auto &obj : _managedObjectArray)
{
obj->release();
}
_managedObjectArray.clear(); // std::vector<cocos2d::Ref *> 类型的数组
}
假设对象通过 create() 创建后没有使用。其默认引用计数为1。在场景第一帧绘制结束时会调用对象的release()方法将引用计数减1至0,因为引用计数为0释放对象所占用内存。同一时候将对象从自己主动回收池中清除掉。

假设对象通过 create() 创建后,通过addChild()增加某个父节点或直接调用对象的retain()函数。将导致对象的引用计数加1。

在场景一帧绘制结束时会调用对象的release()方法将引用计数减1,同一时候将对象从自己主动回收池中清除掉。此时对象的引用计数为1,可是对象已经不在自己主动回收池中,所下面一帧场景绘制清空自己主动回收池时对其没有影响。其占用的内存空间不会被自己主动释放。除非主动调用其release()函数将引用计数再次减1。从而使其引用计数变为0被释放(removeChild()或父节点被释放,这些操作内部实际会调用当前子节点的release()函数)。

所以,通过create()创建对象后。要么将其增加到其它节点中,要么调用对象的retian()函数,否则在其它地方(如事件回调函数)调用时。因为对象在一帧结束时被释放,会发生引用野指针抛出异常的情况。

最佳实践

假设想获得cocos2d-x的自己主动内存管理,就最好不要直接使用对象的 retain() 和 release()函数。假设确实须要那么也要成对的使用,调用一次retain()就必须在合适的时机和地方调用一次其release()函数。


參考资料:
[1]cocos2d-x 高级开发教程   2.3 节

[2]cocos2d-x 3.x 源码

【cocos2d-x 3.x 学习笔记】对象内存管理的更多相关文章

  1. linux kernel学习笔记-5内存管理_转

    void * kmalloc(size_t size, gfp_t gfp_mask); kmalloc()第一个参数是要分配的块的大小,第一个参数为分配标志,用于控制kmalloc()的行为. km ...

  2. XV6学习笔记(2) :内存管理

    XV6学习笔记(2) :内存管理 在学习笔记1中,完成了对于pc启动和加载的过程.目前已经可以开始在c语言代码中运行了,而当前已经开启了分页模式,不过是两个4mb的大的内存页,而没有开启小的内存页.接 ...

  3. Cocos2D-X2.2.3学习笔记3(内存管理)

    本章节介绍例如以下: 1.C/C++内存管理机制 2.引用计数机制 3.自己主动释放机制 1.C/C++内存管理机制 相信仅仅要懂oop的都知道NEW这个keyword吧,这个通俗点说事实上就是创建对 ...

  4. COCOS学习笔记--Cocod2dx内存管理(三)-Coco2d-x内存执行原理

    通过上两篇博客.我们对Cocos引用计数和Ref类.PoolManager类以及AutoreleasePool类已有所了解,那么接下来就通过举栗子来进一步看看Coco2d-x内存执行原理是如何的. / ...

  5. Linux内核学习笔记——内核内存管理方式

    一 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU)把虚拟地址转换为物理 地址,通常以页为单位进行处理.MMU以页大小为单位来管理系统中的也表. 32位系统:页大小4KB 64位系统:页 ...

  6. ios学习笔记之内存管理

    一,内存管理类型定义      1,基本类型  任何C的类型,eg:      int,short,char,long,long long,struct,enum,union等属于基本类型或结构体   ...

  7. 《C#高级编程》学习笔记----c#内存管理--栈VS堆

    本文转载自Netprawn,原文英文版地址 尽管在.net framework中我们不太需要关注内存管理和垃圾回收这方面的问题,但是出于提高我们应用程序性能的目的,在我们的脑子里还是需要有这方面的意识 ...

  8. Cocos2d-x 学习笔记(7) 内存管理 Sprite SpriteFrame Texture2D

    1. 总结 Sprite和SpriteFrame和Texture2D关系紧密,三个类都继承了Ref类.精灵有成员精灵帧和纹理,精灵帧有成员纹理.精灵帧和纹理被精灵帧引用时,引用计数增加,不再被引用时, ...

  9. 嵌入式linux学习笔记1—内存管理MMU之虚拟地址到物理地址的转化

    一.内存管理基本知识 1.S3C2440最多会用到两级页表:以段的方式进行转换时只用到一级页表,以页的方式进行转换时用到两级页表.页的大小有三种:大页(64KB),小页(4KB),极小页(1KB).条 ...

  10. arm-linux学习笔记3-linux内存管理与文件操作

    配置好linux系统之后需要vim配置一下,有助于我们的编程,主要的配置如下 在/etc/vim/vimrc文件中 "显示行号 set number "自动缩进 set autoi ...

随机推荐

  1. AC日记——The Child and Sequence codeforces 250D

    D - The Child and Sequence 思路: 因为有区间取模操作所以没法用标记下传: 我们发现,当一个数小于要取模的值时就可以放弃: 凭借这个来减少更新线段树的次数: 来,上代码: # ...

  2. (14)oracle数据字典

    http://czmmiao.iteye.com/blog/1258462 数据字典解释 1.user_tables 查询用户所拥有的所有表 select table_name from user_t ...

  3. Redis数据类型、两种模型、事务、内部命令

    1.redis数据类型 a.字符串,使用场景:常规key-value缓存应用 set name lixiang get name append name 123 # 字符串追加 mset key va ...

  4. luogu P1340 兽径管理

    题目描述 约翰农场的牛群希望能够在 N 个(1<=N<=200) 草地之间任意移动.草地的编号由 1到 N.草地之间有树林隔开.牛群希望能够选择草地间的路径,使牛群能够从任一 片草地移动到 ...

  5. [BZOJ1038][ZJOI2008]瞭望塔(半平面交)

    1038: [ZJOI2008]瞭望塔 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2999  Solved: 1227[Submit][Statu ...

  6. Flash3D学习计划(一)——3D渲染的一般管线流程

    一:什么是渲染管线 渲染管线也称为渲染流水线,是显示芯片内部处理图形信号相互独立的并行处理单元.一个流水线是一序列可以并行和按照固定顺序进行的阶段.每个阶段都从它的前一阶段接收输入,然后把输出发给随后 ...

  7. 六. 异常处理4.try和catch的使用

    尽管由Java运行时系统提供的默认异常处理程序对于调试是很有用的,但通常你希望自己处理异常.这样做有两个好处.第一,它允许你修正错误.第二,它防止程序自动终止.大多数用户对于在程序终止运行和在无论何时 ...

  8. 我学MSMQ(二)

      上次我主要学习的是MSMQ的基本的概念.安装消息队列和新建了一个简单的队列.      现在我就继续学习关于消息队列的接收先还是概念         消息的接收又分成同步和异步方式两种,同步接收在 ...

  9. ASIHTTPRequest框架使用总结系列之阿堂教程5(上传数据)

    在上篇文章中,阿堂和网友们分享了如何用ASIHTTPRequest框架下载数据的实例,本篇阿堂将数据介绍如何用ASIHTTPRequest框架上传数据的应用实例.       数据上传是通过ASIHT ...

  10. etcd集群日常维护

    配置文件和启动参数说明 命令行 | 配置文件 | 说明 data-dir | ETCD_DATA_DIR | 指定节点的数据存储目录,包括节点ID,集群ID,集群初始化配置,Snapshot文件,若未 ...