8、Cocos2dx 3.0游戏开发找小三之3.0版本的内存管理

重开发者的劳动成果,转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/27693365

复杂的内存管理
移动设备上的硬件资源十分有限,内存尤为宝贵,开发者必须十分慎重地利用内存,避免不必要的消耗,更要防止内存泄 漏。
基于 Cocos2d-iPhone 的 Objective-C 风格的内存管理是 Cocos2d-x 的一个特色。
把 Objective-C 的内存管理方式引入 C++,使得游戏开发的内存管理难度下降了 个层次。

内存管理一直是一个很复杂且不易处理的问题,开发者必须充分考虑分配回收的方式、
以及分配回收内存的时机,针对堆和栈做不同的优化处理。
内存管理的核心是动态分配的对象所使用的内存必须保证在使用完毕后有效地释放掉,
即面向对象语言中的管理对象的生命周期。

C++中变量的内存空间的分配问题,我们在c++中写一个类,可以在栈上分配内存空间也可以使用new在堆上分配内存空间,如果类对象是在栈上分配的内存空间,这个内存空间的管理就不是我们的事了,但如果是在堆上分配的内存空间,当然需要我们来手动的delete了!

由于 C++是一个较为底层 的语言,其设计上不包含任何智能管理内存的机制。

一个对象在使用完毕后必须被回收,然而在复杂的程序中,对象所有权在不同程序片段间传递或共享,
使得确定回收的时机十分困难,因此内存管理成为了程序员十分头疼的问题。
另一方面,过于零散的对象分配回收可能导致堆中的内存碎片化,降低内存的使用效率。
因此,我们需要一个合适的机制来缓解这个问题。

比如cocos2dx采用的是在堆上分配内存空间,既然在堆上分配内存空间,那么如何管理这个内存空间,什么时候应该释放就是个问题了!在程序中,当我们创建了一个对象的时候,这块内存空间经常是被不同的对象引用,如果删除的早了,有对象还在引用这块内存空间那么程序必然要崩溃!
下面看cocos2dx是怎么解决的?

目前现有的智能内存管理技术
目前,主要有两种实现智能管理内存的技术,一是引用计数,一是垃圾回收。
引用计数:它是一种很有效的机制,通过给 每个对象维护一个引用计数器,记录该对象当前被引用的次数。
当对象增加一次引用时,计数器加 1;而对象失去一次引用 时,计数器减 1;当引用计数为 0 时,标志着该对象的生命周期结束,自动触发对象的回收释放。
引用计数的重要规则是每 一个程序片段必须负责任地维护引用计数,在需要维持对象生存的程序段的开始和结束分别增加和减少一次引用计数,这 样我们就可以实现十分灵活的智能内存管理了。
实际上,这与 new 和 delete 的配对使用十分类似,但是很巧妙地将生成和 回收的事件转换成了使用和使用结束的事件。对于程序员来说,维护引用计数比维护生命周期信息轻松了许多。
引用计数 解决了对象的生命周期管理问题,但堆碎片化和管理烦琐的问题仍然存在。

垃圾回收:它通过引入一种自动的内存回收器, 试图将程序员从复杂的内存管理任务中完全解放出来。
它会自动跟踪每一个对象的所有引用,以便找到所有正在使用的对 象,然后释放其余不再需要的对象。
垃圾回收器还可以压缩使用中的内存,以缩小堆所需要的工作空间。
垃圾回收可以防 止内存泄露,有效地使用可用内存。

但是,垃圾回收器通常是作为一个单独的低级别的线程运行的,在不可预知的情况下 对内存堆中已经死亡的或者长时间没有使用过的对象进行清除和回收,程序员不能手动指派垃圾回收器回收某个对象。
收机制包括分代复制垃圾回收、标记垃圾回收和增量垃圾回收。

Cocos2d-x使用的内存管理机制
Cocos2d-x 来源于 Cocos2d-iPhone,因此为了与 Objective-C 一致,Cocos2d-x 也采用了引用计数与自动回收的内存管理机制 为了实现对象的引用计数记录,  Cocos2d-x 3.0实现了新的根类Ref, 引擎中的所有类都派生自Ref。
在"cocos/base/CCRef.h头文件中我们可以看到 Clonable类和Ref类的定义:
/** Interface that defines how to clone an Ref */
class CC_DLL Clonable
{
public:
 /** returns a copy of the Ref */
    virtual Clonable* clone() const = 0;
    /**
     * @js NA
     * @lua NA
     */
 virtual ~Clonable() {};

    /** returns a copy of the Ref.
     @deprecated Use clone() instead
     */
    CC_DEPRECATED_ATTRIBUTE Ref* copy() const
    {
        // use "clone" instead
        CC_ASSERT(false);
        return nullptr;
    }
};

class CC_DLL Ref
{
public:
    void retain();
    
    void release();

    Ref* autorelease();

    unsigned int getReferenceCount() const;
    
protected:
    Ref();
    
public:
    virtual ~Ref();
    
protected:
    /// count of references
    unsigned int _referenceCount;
    
    friend class AutoreleasePool;
    
#if CC_ENABLE_SCRIPT_BINDING
public:
    /// object id, ScriptSupport need public _ID
    unsigned int _ID;
    /// Lua reference id
    int _luaID;
#endif
};

Clonable类:定义怎样复制Ref类的接口

virtual Clonable *clone() const =0;   //克隆函数是纯虚函数,必须在子类重写
例:
virtual __Array *clone() const;
实现:
__Array* __Array::clone() const
{
 __Array* ret = new __Array();
    ret->autorelease();
    ret->initWithCapacity(this->data->num > 0 ? this->data->num : 1);
return ret;
}

Ref类

void retain();   //保持所有权,增加Ref的引用次数

void release(); //减少引用次数,引用计数直接减一,如果引用次数等于0,立即释放

Ref* autorelease(); //自动释放,将该物体加入自动释放池,当引用计数为0时自动释放


举个错误使用例子:

auto obj=Node::create();  //Ref=1,但是当前Node已经在自动释放缓冲池中

obj->autorelease();       //错误:如果你调用autorelease()很多次,你必须retain()


从源码中可以看到,每个对象包含一个用来控制生命周期的引用计数器,它就是 Ref的成员变量_referenceCount。

我们可以通过 getReferenceCount()方法获得对象当前的引用计数值。

在对象通过构造函数创建的时候,该引用值被赋为 1,表示对象由创建者所引用。

在其他地方需要引用对象时,我们会调用 retain()方法,令其引用计数增 1,表示获取该对象的引用权;

在引用 结束的时候调用 release()方法,令其引用计数值减 1,表示释放该对象的引用权。


相对于IOS SDK把这个内存计数封装到了NSAutoreleasePool中。

在cocos2d-x同样有一套CCAutoreleasePool与之相对应。

两者的用法基本一样。


这就要说到这个很有意思的方法:autorelease(),

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

其作用是将对象放入自动回收池(AutoreleasePool)。


AutoreleasePool::~AutoreleasePool()
{
    CCLOGINFO("deallocing AutoreleasePool: %p", this);
    clear();
    
    PoolManager::getInstance()->pop();
}

当回收池自身被释放 的时候, 它就会对池中的所有对象执行一次 release()方法, 实现灵活的垃圾回收。 

回收池可以手动创建和释放。 

除此之外, 引擎在每次游戏循环开始之前也会创建一个回收池,在循环结束后释放回收池。

因此,即使我们没有手工创建和释放回收 池,每一帧结束的时候,

自动回收池中的对象也都会被执行一次 release()方法。

我们马上就会领略到 autorelease()的方 便之处。


下面是一个简单的例子。

可以看到,对象创建后,引用计数为 1;

执行一次 retain()后,引用计数为 2;

执行一次 release()后,引用计数回到 1;

执行一次 autorelease()后,对象的引用计数值并没有立即减 1,但是在下一帧开始前,对象会被释放掉。


下面是测试代码:

mistress = new CCSprite();
mistress->init();
CCLog("retainCount after init: %d", mistress->getReferenceCount());
mistress->retain();
CCLog("retainCount after retain: %d", mistress->getReferenceCount()); 
mistress->release();
CCLog("retainCount after release: %d", mistress->getReferenceCount());
mistress->autorelease();
CCLog("retainCount after autorelease: %d", mistress->getReferenceCount());

控制台显示的日志如下:

Cocos2d: retainCount after init: 1

Cocos2d: retainCount after retain: 2

Cocos2d: retainCount after release: 1

Cocos2d: retainCount after autorelease: 1


我们已经知道,调用了 autorelease()方法的对象,将会在自动回收池释放的时候被释放一次。

虽然,Cocos2d-x 已经保证每一帧结束后释放一次回收池,并在下一帧开始前创建一个新的回收池,但是我们也应该

考虑到回收池本身维护着一个将要执行释放操作的对象列表,如果在一帧之内生成了大量的 autorelease 对象,将会导致回收池性能下降。

因此,在生成 autorelease 对象密集的区域(通常是循环中)的前后,我们最好可以手动创建并释放一个回收池。


我们可以通过回收池管理器 PoolManager的 push()或 pop()方法来创建或释放回收池,其中的 PoolManager 也是一个单例对象,可以通过PoolManager::getInstance()获取该单例对象。


在这里,再通过段简单的代码来分析自动回收池的嵌套机制:

PoolManager::getInstance()->push(this);
for(int i=0; i<n; i++)
{
 String* dataItem = String::createWithFormat("%d", Data[i]); 
 stringVector->push_back(dataItem); 
 }
PoolManager::getInstance()->pop();

这段代码包含了一个执行 n 次的循环,每次都会创建一个 autorelease 对象 String。

为了保持回收池的性能,我们在循环前使用 push()方法创建了一个新的回收池,在循环结束后使用 pop()方法释放刚才创建的回收池。


不难看出,自动回收池是可嵌套的。

通常,引擎维护着一个回收池,所有的 autorelease 对象都添加到了这个池中。

多个自动回收池排列成栈结构,当我们手动创建了回收池后,回收池会压入栈的顶端,autorelease 对象仅添加到顶端的池中。当顶层的回收池被弹出释放时, 它内部所有的对象都会被释放一次, 此后出现的 autorelease 对象则会添加到下一个池中。


在自动回收池嵌套的情况下,每一个对象是如何加入自动回收池以及如何释放的,相关代码如下所示:

//步骤 a
obj1->autorelease();
obj2->autorelease(); 

//步骤 b
PoolManager::getInstance()->push(this);

//步骤 c
for(int i=0; i<n; i++) {
 obj_array[i]->autorelease(); 
 }
//步骤 d
PoolManager::getInstance()->pop();
//步骤 e
obj3->autorelease();

上述代码的具体过程如图所示:

当执行完步骤 a 时,obj1 与 obj2 被加入到回收池 1 中,如图 a 所示;

步骤 b 创建了一个新的回收池,此时回收池 2 接管所有 autorelease 操作,如图b 所示;

步骤 c 是一个循环,其中把 n 个对象加入回收池 2 中,如图c 所示;

步骤 d 释放了回收池 2,因此回收池 2 中的 n 个对象都被释放了一次,同时回收池 1 接管autorelease 操作;

步骤 e 调用 obj3 的 autorelease()方法,把 obj3 加入回收池 1 中,如图e 所示。

8、Cocos2dx 3.0游戏开发找小三之3.0版本的内存管理_第1张图片


郝萌主友情提示:
cocos2d-x引擎给开发者提供了极大的方便,但是不要乱用噢、、、

你可能感兴趣的:(内存管理,内存泄漏,游戏开发,游戏引擎,cocos2d-x3.0)