Cocos2d-x使用的内存管理方式是引用计数。引用计数是一种非常有效的机制。通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数。当对象添加一次引用时,计数器加1;而对象失去一次引用时。计数器减1;当引用计数为0时,标志着该对象的生命周期结束,自己主动触发对象的回收释放。引用计数的重要规则是每个程序片段必须负责任地维护引用计数,在须要维持对象生存的程序段的開始和结束分别添加和降低一次引用计数,这样就能够实现十分灵活的内存管理。

接下来看一下Cocos2d-x 3.1 版本号的源代码是怎么实现引用计数的。

一、Ref

我们都知道差点儿每一个类都继承一个类Ref,打开CCRef.h查看Ref类。去掉其他与引用计数无关的。能够简化为:

// CCRef.h
class CC_DLL Ref
{
public:
void retain(); // 引用计数加1
void release(); // 引用计数减1
Ref* autorelease(); // 将对象交给自己主动释放池 unsigned int getReferenceCount() const; // 获取当前的引用计数 protected:
Ref(); // 构造函数,这里的权限为protected,说明自己不能实例化,子类能够实例化 public:
virtual ~Ref(); protected:
unsigned int _referenceCount; // 引用计数 friend class AutoreleasePool; // 这个先别管
};
// CCRef.cpp
Ref::Ref(): _referenceCount(1) // new一个对象时,引用计数加1
{
} Ref::~Ref()
{
} void Ref::retain()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
++_referenceCount;
} void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should greater than 0");
--_referenceCount; if (_referenceCount == 0) // 引用为0时说明没有调用该对象,此时delete对象
{
delete this;
}
} Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this); // 交给自己主动释放池管理
return this;
} unsigned int Ref::getReferenceCount() const
{
return _referenceCount;
}

总结一下,Ref类就做了几件事:

1、Ref自己不能实例化,仅仅能由子类实例化;

2、创建是引用计数为1。

3、调用retain引用计数加1。

4、调用release引用计数减1;

5、调用autorelease并没有使引用计数减1。而是交给自己主动释放池来管理。

那么自己主动释放池是什么呢?肯定跟autorelease方法里面的PoolManager有关。

打开CCAutoreleasePool.h文件查看。发现有两个类,一个是AutoreleasePool,一个是PoolManager,从字面意思看,AutoreleasePool就是自己主动释放池,而PoolManager就是池管理器,这些思路有点清晰了:

1、调用autorelease后对象交给AutoreleasePool来管理。

2、PoolManager是用来管理AutoreleasePool的,说明能够有多个池。

二、AutoreleasePool

接下来一步步看,先看AutoreleasePool自己主动释放池。看简化版本号的:

// AutoreleasePool.h
class CC_DLL AutoreleasePool
{
public:
AutoreleasePool(); // 疑问1:不要在堆上创建,而应该在栈上创建(为什么呢?等下解释)
~AutoreleasePool(); void addObject(Ref *object); // 增加一个Ref对象到释放池
void clear(); // 清理自己主动释放池 private:
std::vector<Ref*> _managedObjectArray; // 用来保存这个自己主动释放池里面增加的全部Ref对象
};
// AutoreleasePool.cpp
AutoreleasePool::AutoreleasePool()
{
_managedObjectArray.reserve(150); // 1、设置容器大小为150
PoolManager::getInstance()->push(this); // 2、新建一个释放池时就增加了释放池管理器中(能够临时放着,等看了PoolManager再回来看)
} AutoreleasePool::~AutoreleasePool()
{
clear(); // 1、清理释放池 PoolManager::getInstance()->pop(); // 2、将释放池从释放池管理器中删除
} void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object); // 增加一个Ref对象到释放池中
} void AutoreleasePool::clear()
{
for (const auto &obj : _managedObjectArray) // 1、调用全部自己主动释放对象的release函数,注意:仅仅有当引用计数为0时才会delete对象,同样对象增加几次就会release几次
{
obj->release();
}
_managedObjectArray.clear(); // 2、清除容器
}

总结一下,AutoreleasePool类就做了几件事:

1、维持一个保存Ref对象的队列,这些Ref对象调用autorelease就会加到该队列,调用addObject函数加入;

2、clear函数对AutoreleasePool管理的全部Ref运行一次release操作。仅仅有当引用计数为0时对象才会delete,增加几次就运行几次release操作。

三、PoolManager

PoolManager是管理释放池的,在AutoreleasePool用到push和pop方法,能够猜到PoolManager应该维持一个存放释放池的栈:

// PoolManager.h
class CC_DLL PoolManager
{
public:
static PoolManager* getInstance(); // PoolManager是个单例模式 static void destroyInstance(); // 删除单例模式创建的对象 AutoreleasePool *getCurrentPool() const; // 获取当前的释放池 private:
PoolManager();
~PoolManager(); void push(AutoreleasePool *pool); // 压入一个释放池
void pop(); // 弹出一个释放池 static PoolManager* s_singleInstance; std::deque<AutoreleasePool*> _releasePoolStack; // 存放自己主动释放池的栈
AutoreleasePool *_curReleasePool; // 当前的自己主动释放池
};
// PoolManager.cpp
PoolManager* PoolManager::s_singleInstance = nullptr; // 获取单例模式时,假设还没创建,则会创建两个释放池并加入到池管理器中
PoolManager* PoolManager::getInstance()
{
if (s_singleInstance == nullptr)
{
s_singleInstance = new PoolManager(); // 第一个池:AutoreleasePool构造函数会将构造的池加入到池管理器中
s_singleInstance->_curReleasePool = new AutoreleasePool();
// 第二个池:将new出来的释放池再一次压入池管理器中
s_singleInstance->_releasePoolStack.push_back(s_singleInstance->_curReleasePool);
}
return s_singleInstance;
} // delete单例模式创建的对象
void PoolManager::destroyInstance()
{
delete s_singleInstance;
s_singleInstance = nullptr;
} PoolManager::PoolManager()
{
} // 析构函数
PoolManager::~PoolManager()
{
while (!_releasePoolStack.empty())
{
AutoreleasePool* pool = _releasePoolStack.back();
_releasePoolStack.pop_back(); // 1、弹出自己主动释放池 delete pool; // 2、delete自己主动释放池对象
}
} // 获取当前释放池
AutoreleasePool* PoolManager::getCurrentPool() const
{
return _curReleasePool;
} void PoolManager::push(AutoreleasePool *pool)
{
_releasePoolStack.push_back(pool); // 1、压入释放池
_curReleasePool = pool; // 2、设为当前释放池
} // 弹出栈顶释放池。并将当前释放池指向新的栈顶释放池
void PoolManager::pop()
{
CC_ASSERT(_releasePoolStack.size() >= 1); _releasePoolStack.pop_back(); if (_releasePoolStack.size() > 1)
{
_curReleasePool = _releasePoolStack.back();
}
}

貌似PoolManager功能更加简单。就是管理释放池。

四、内存管理思路

1、autorelease

new一个对象的时候,调用其autorelease将对象交给自己主动释放池管理

Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurReleasePool()->addObject(this);
return this;
}

2、PoolManager::getInstance()

在获取单例对象时。假设不存在则会创建一个PoolManager对象,这时候会加入两个释放池

引擎自己会维持两个默认的释放池,假设我们没有手动创建释放池。则autorelease对象都加入到栈顶默认释放池。

事实上我还没弄懂这里为什么要有两个默认的释放池,一个也能够的。

3、getCurReleasePool()获取的是当前释放池。addObject()将Ref对象增加当前释放池中。

void AutoreleasePool::addObject(Ref* object)
{
_managedObjectArray.push_back(object);
}

这样。每个调用autorelease的Ref对象都会加入到_managedObjectArray中。

4、自己主动释放池的对象是怎么释放的?看AutoreleasePool:clear()函数

这里循环_managedObjectArray调用里面对象的release。调用1次release,引用计数就减1,当引用计数为0时就delete该对象。

你肯定非常疑惑。在哪里会调用clear函数呢,~AutoreleasePool()会调用,可是那是在delete的时候释放的,我们看到Director类的主循环:

看到这里就明确了吧。每一次主循环都会调用clear来release自己主动释放池的对象。而每一帧会运行一次主循环,也就是每一帧都会清除一次。

五、手动创建释放池

我们已经知道。调用了autorelease()方法的对象将会在自己主动释放池池释放的时候被释放一次。尽管Cocos2d-x已经保证每一帧结束后释放一次释放池,可是假设在一帧之内生成了大量的autorelease对象。将会导致释放池性能下降。因此在生存autorelease对象密集的区域(一般是循环中)的前后,最后手动创建一个释放池。

{
AutoreleasePool pool1; // 手动创建一个释放池
for ()
{
ref->autorelease(); // 循环里面运行autorelease。这些对象会加入到pool1中
}
}

此时。引擎维护三个释放池,我们知道每一帧结束时会运行当前释放池的clear(),所以上面的那些对象就会在第一帧结束时被释放。而那些放在引擎默认释放池的autorelease对象就会在下一帧被释放。错开了释放的时间,这样就不会减少释放池的性能。

看到上面的代码,你会感到疑惑:为什么仅仅有创建释放池。而没有释放。还记得在AutoreleasePool.h中AutoreleasePool构造函数的凝视吗:不要在堆上创建。而应该在栈上。我们知道,new出来对象必须手动delete才干释放,而在栈上的变量。当作用域消失就会释放,如上面的pool1,当运行到最后一个“}”时就会调用其析构函数,看看AutoreleasePool构造和析构函数做了些什么:

创建一个AutoreleasePool是会被push到PoolManager中。而作用域系消失时就会运行析构函数,调用pop从PoolManager中删除该释放池。

这样,在这个局部作用域pool1之间全部的内存管理实际上是交给了AutoreleasePool来完毕。真的是好方法。

假设上面哪里说错了。能够指出来大家讨论讨论。

网上有非常多关于Cocos2d-x内存管理的教程了,大家假设认为我的非常难理解的话能够查考下以下几篇精华:

1、内存管理源代码分析http://cn.cocos2d-x.org/tutorial/show?id=850

2、Cocos2d-x内存管理浅说 http://www.cocoachina.com/newbie/basic/2013/0528/6290.html

3、Cocos2d-x内存管理的一种实现 http://www.cocoachina.com/applenews/devnews/2013/0531/6315.html

4、深入理解Cocos2d-x内存管理 http://www.cocoachina.com/applenews/devnews/2013/0621/6455.html

Cocos2d-x 3.1 内存管理机制的更多相关文章

  1. 【Cocos2d-x 3.x】内存管理机制与源码分析

    侯捷先生说过这么一句话 :  源码之前,了无秘密. 要了解Cocos2d-x的内存管理机制,就得阅读源码. 接触Cocos2d-x时, Cocos2d-x的最新版本已经到了3.2的时代,在学习Coco ...

  2. cocos2d-x 3.0 内存管理机制

    ***************************************转载请注明出处:http://blog.csdn.net/lttree************************** ...

  3. 浅谈Linux内存管理机制

    经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...

  4. ARC内存管理机制详解

    ARC在OC里面个人感觉又是一个高大上的牛词,在前面Objective-C中的内存管理部分提到了ARC内存管理机制,ARC是Automatic Reference Counting---自动引用计数. ...

  5. 深入了解C#系列:谈谈C#中垃圾回收与内存管理机制

    今天抽空来讨论一下.Net的垃圾回收与内存管理机制,也算是完成上个<WCF分布式开发必备知识>系列后的一次休息吧.以前被别人面试的时候问过我GC工作原理的问题,我现在面试新人的时候偶尔也会 ...

  6. Spark 1.6以后的内存管理机制

     Spark 内部管理机制 Spark的内存管理自从1.6开始改变.老的内存管理实现自自staticMemoryManager类,然而现在它被称之为"legacy". " ...

  7. python的内存管理机制

    先从较浅的层面来说,Python的内存管理机制可以从三个方面来讲 (1)垃圾回收 (2)引用计数 (3)内存池机制 一.垃圾回收: python不像C++,Java等语言一样,他们可以不用事先声明变量 ...

  8. Java虚拟机内存管理机制

    自动内存管理机制 Java虚拟机(JVM)在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有的区 ...

  9. 了解linux内存管理机制(转)

    今天了解了下linux内存管理机制,在这里记录下,原文在这里http://ixdba.blog.51cto.com/2895551/541355 根据自己的理解画了张图: 下面是转载的内容: 一 物理 ...

  10. object-c(oc)内存管理机制详解

    1.内存的创建和释放 让我们以Object-c世界中最最简单的申请内存方式展开,谈谈关于一个对象的生命周期.首先创建一个对象: 1 2 3 //“ClassName”是任何你想写的类名,比如NSStr ...

随机推荐

  1. 【LeetCode】111 - Minimum Depth of Binary Tree

    Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shor ...

  2. vs2008编译boost

    vs2008编译boost [一.Boost库的介绍] Boost库是一个经过千锤百炼.可移植.提供源代码的C++库,作为标准库的后备,是C++标准化进程的发动机之一.Boost库由C++标准委员会库 ...

  3. WebApi参数传递

    c# webapi的参数传递方式:1.查询字符串(query string):2.内容主体(content body) 当然也有cookie或url部分或头部信息(header)等其它传方式,这里仅讨 ...

  4. apache 2.4 针对某个文件限速

    http://httpd.apache.org/docs/2.4/mod/mod_ratelimit.html <Location "/downloads/*.mp4"> ...

  5. Django 1.6 最佳实践: 如何正确使用 Signal(转)

    原文:http://www.weiguda.com/blog/38/ 如何正确的使用signal: 简单回答是: 在其他方法无法使用的情况下, 才最后考虑使用signal. 因为新的django开发人 ...

  6. Accessor Search Implementation Details

    [Accessor Search Implementation Details] Key-value coding attempts to use accessor methods to get an ...

  7. 文本读写vs二进制读写

    [文本读写vs二进制读写] 在学习C语言文件操作后,我们都会知道打开文件的函数是fopen,也知道它的第二个参数是 标志字符串.其中,如果字符串中出现'b',则表明是以打开二进制(binary)文件, ...

  8. HDU 1847 Good Luck in CET-4 Everybody!(找规律,或者简单SG函数)

    Good Luck in CET-4 Everybody! Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  9. Leetcode237:Delete Node in a Linked List

    Write a function to delete a node (except the tail) in a singly linked list, given only access to th ...

  10. 找回windows 8 中隐藏的Aero Lite主题

    在windows 8 预览版中,有一款主题Aero Lite,此主题已经不提供Aero glass的效果,还有反射模糊,但在windows 8 RTM中这个主题被隐藏了,我们在个性化里面看不到Aero ...