Cocos2d-x的内存管理总结

Cocos2d-x引擎的核心是用C++编写的,那对于所有使用该引擎的游戏开发人员来说,内存管理是一道绕不过去的坎。

关于Cocos2d-x内存管理,网上已经有了许多参考资料,有些资料写的颇为详实,因为在内存管理这块我不想多费笔墨,只是更多的将思路描述清楚。

一、对象内存引用计数

Cocos2d-x内存管理的基本原理就是对象内存引用计数,Cocos2d-x将内存引用计数的实现放在了顶层父类CCObject中,这里将涉及引用计数的CCObject的成员和方法摘录出来:
复制代码 代码如下:
class CC_DLL CCObject : public CCCopying
{
public:
   … …
protected:
    // count of references
    unsigned int        m_uReference;
    // count of autorelease
    unsigned int        m_uAutoReleaseCount;
public:
    void release(void);
    void retain(void);
    CCObject* autorelease(void);
    … ….
}

CCObject::CCObject(void)
: m_nLuaID(0)
, m_uReference(1) // when the object is created, the reference count of it is 1
, m_uAutoReleaseCount(0)
{
  … …
}

void CCObject::release(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");
    –m_uReference;

if (m_uReference == 0)
    {
        delete this;
    }
}

void CCObject::retain(void)
{
    CCAssert(m_uReference > 0, "reference count should greater than 0");

++m_uReference;
}

CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}

先不考虑autorelease与m_uAutoReleaseCount(后续细说)。计数的核心字段是m_uReference,可以看到:

* 当一个Object初始化(被new出来时),m_uReference = 1;
* 当调用该Object的retain方法时,m_uReference++;
* 当调用该Object的release方法时,m_uReference–,若m_uReference减后为0,则delete该Object。

二、手工对象内存管理

在上述对象内存引用计数的原理下,我们得出以下Cocos2d-x下手工对象内存管理的基本模式:
复制代码 代码如下:
CCObject *obj = new CCObject();
obj->init();
…. …
obj->release();

在Cocos2d-x中CCDirector就是一个手工内存管理的典型:

CCDirector* CCDirector::sharedDirector(void)
{
    if (!s_SharedDirector)
    {
        s_SharedDirector = new CCDisplayLinkDirector();
        s_SharedDirector->init();
    }

return s_SharedDirector;
}

void CCDirector::purgeDirector()
{
    … …
    // delete CCDirector
    release();
}

三、自动对象内存管理

所谓的“自动对象内存管理”,指的就是哪些不再需要的object将由Cocos2d-x引擎替你释放掉,而无需你手工再调用Release方法。

自动对象内存管理显然也要遵循内存引用计数规则,只有当object的计数变为0时,才会释放掉对象的内存。

自动对象内存管理的典型模式如下:
复制代码 代码如下:
CCYourClass *CCYourClass::create()
{
    CCYourClass*pRet = new CCYourClass();
    if (pRet && pRet->init())
    {
        pRet->autorelease();
        return pRet;
    }
    else
    {
        CC_SAFE_DELETE(pRet);
        return NULL;
    }
}

一般我们通过一个单例模式创建对象,与手工模式不同的地方在于init后多了一个autorelease调用。这里再把autorelease调用的实现摘录一遍:
复制代码 代码如下:
CCObject* CCObject::autorelease(void)
{
    CCPoolManager::sharedPoolManager()->addObject(this);
    return this;
}

追溯addObject方法:
复制代码 代码如下:
// cocoa/CCAutoreleasePool.cpp

void CCPoolManager::addObject(CCObject* pObject)
{
    getCurReleasePool()->addObject(pObject);
}

void CCAutoreleasePool::addObject(CCObject* pObject)
{
    m_pManagedObjectArray->addObject(pObject);

CCAssert(pObject->m_uReference > 1, "reference count should be greater than 1");
    ++(pObject->m_uAutoReleaseCount);
    pObject->release(); // no ref count, in this case autorelease pool added.
}

// cocoa/CCArray.cpp
void CCArray::addObject(CCObject* object)                                                                                                  
{                                                                                                                                         
    ccArrayAppendObjectWithResize(data, object);                            
}

// support/data_support/ccCArray.cpp
void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)                                                                         
{                                                                                                                  
    ccArrayEnsureExtraCapacity(arr, 1);                                                             
    ccArrayAppendObject(arr, object);                                        
}

void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
    CCAssert(object != NULL, "Invalid parameter!");
    object->retain();
    arr->arr[arr->num] = object;
    arr->num++;
}

调用层次挺深,涉及的类也众多,这里归纳总结一下。

Cocos2d-x的自动对象内存管理基于对象引用计数以及CCAutoreleasePool(自动释放池)。引用计数前面已经说过了,这里单说自动释放池。Cocos2d-x关于自动对象内存管理的基本类层次结构如下:
复制代码 代码如下:
    CCPoolManager类 (自动释放池管理器)
        – CCArray*    m_pReleasePoolStack; (自动释放池栈,存放CCAutoreleasePool类实例)

CCAutoreleasePool类
        – CCArray*    m_pManagedObjectArray; (受管对象数组)

CCObject关于内存计数以及自动管理有两个字段:m_uReference和m_uAutoReleaseCount。前面在手工管理模式下,我只提及了m_uReference,是m_uAutoReleaseCount该亮相的时候了。我们沿着自动释放对象的创建步骤来看看不同阶段,这两个重要字段的值都是啥,代表的是啥含义:
复制代码 代码如下:
CCYourClass*pRet = new CCYourClass();    m_uReference = 1; m_uAutoReleaseCount = 0;
pRet->init();                           m_uReference = 1; m_uAutoReleaseCount = 0;
pRet->autorelease();                   
    m_pManagedObjectArray->addObject(pObject); m_uReference = 2; m_uAutoReleaseCount = 0;
    ++(pObject->m_uAutoReleaseCount);          m_uReference = 2; m_uAutoReleaseCount = 1;
    pObject->release();                        m_uReference = 1; m_uAutoReleaseCount = 1;

在调用autorelease之前,两个值与手工模式并无差别,在autorelease后,m_uReference值没有变,但m_uAutoReleaseCount被加1。

m_uAutoReleaseCount这个字段的名字很容易让人误解,以为是个计数器,但实际上绝大多数时刻它是一个标识的角色,以前版本代码中有一个布尔字段m_bManaged,似乎后来被m_uAutoReleaseCount替换掉了,因此m_uAutoReleaseCount兼有m_bManaged的含义, 也就是说该object是否在自动释放池的控制之下,如果在自动释放池的控制下,自动释放池会定期调用该object的release方法,直到该 object内存计数降为0,被真正释放。否则该object不能被自动释放池自动释放内寸,需手工release。这个理解非常重要,再后面我们能用到这个理解。

四、自动释放时机

通过autorelease我们已经将object放入autoreleasePool中,那究竟何时对象会被释放呢?答案是每帧执行一次自动内存对象释放操作。

在“Hello,Cocos2d-x”一文中,我们讲过整个Cocos2d-x引擎的驱动机制在于GLThread的guardedRun函数,后者会 “死循环”式(实际帧绘制频率受到屏幕vertsym信号的影响)的调用Render的onDrawFrame方法实现,而最终程序会进入 CCDirector::mainLoop方法中,也就是说mainLoop的执行频率是每帧一次。我们再来看看mainLoop的实现:

复制代码 代码如下:
void CCDisplayLinkDirector::mainLoop(void)
{
    if (m_bPurgeDirecotorInNextLoop)
    {
        m_bPurgeDirecotorInNextLoop = false;
        purgeDirector();
    }
    else if (! m_bInvalid)
     {
         drawScene();

// release the objects
         CCPoolManager::sharedPoolManager()->pop();
     }
}

这次我们要关注的不是drawScene,而是 CCPoolManager::sharedPoolManager()->pop(),显然在游戏未退出 (m_bPurgeDirecotorInNextLoop决定)的条件下,CCPoolManager的pop方法每帧执行一次,这就是自动释放池执行的起点。
复制代码 代码如下:
void CCPoolManager::pop()
{
    if (! m_pCurReleasePool)
    {
        return;
    }

int nCount = m_pReleasePoolStack->count();

m_pCurReleasePool->clear();

if(nCount > 1)
      {
        m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
        m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount – 2);
    }
}

真正释放对象的方法是m_pCurReleasePool->clear()。
复制代码 代码如下:
void CCAutoreleasePool::clear()
{
    if(m_pManagedObjectArray->count() > 0)
    {
        CCObject* pObj = NULL;
        CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
        {
            if(!pObj)
                break;

–(pObj->m_uAutoReleaseCount);
        }

m_pManagedObjectArray->removeAllObjects();
    }
}

void CCArray::removeAllObjects()    
{  
    ccArrayRemoveAllObjects(data);                   
}

void ccArrayRemoveAllObjects(ccArray *arr)                   
{                      
    while( arr->num > 0 )                     
    {                   
        (arr->arr[--arr->num])->release();              
    }                   
}

不出预料,当前自动释放池遍历每个“受控制”Object,–m_uAutoReleaseCount,并调用该object的release方法。

我们接着按释放流程来看看m_uAutoReleaseCount和m_uReference值的变化:
复制代码 代码如下:
CCPoolManager::sharedPoolManager()->pop();  m_uReference = 0; m_uAutoReleaseCount = 0;

五、自动释放池的初始化

自动释放池本身是何时出现的呢?回顾一下Cocos2d-x引擎的初始化过程(android版),引擎初始化实在Render的onSurfaceCreated方法中进行的,我们不难追踪到以下代码:
复制代码 代码如下:
//hellocpp/jni/hellocpp/main.cpp
Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit {

//这里CCDirector第一次被创建
    if (!CCDirector::sharedDirector()->getOpenGLView())
    {
        CCEGLView *view = CCEGLView::sharedOpenGLView();
        view->setFrameSize(w, h);

AppDelegate *pAppDelegate = new AppDelegate();
        CCApplication::sharedApplication()->run();
    }
}

CCDirector* CCDirector::sharedDirector(void)
{
    if (!s_SharedDirector)
    {
        s_SharedDirector = new CCDisplayLinkDirector();
        s_SharedDirector->init(); 
    }

return s_SharedDirector;
}

bool CCDirector::init(void)
{
    setDefaultValues();

… …

// create autorelease pool
    CCPoolManager::sharedPoolManager()->push();

return true;
}

六、探寻Cocos2d-x内核对象的自动化内存释放

前面我们基本了解了Cocos2D-x的自动化内存释放原理。如果你之前翻看过一些Cocos2d-x的内核源码,你会发现很多内核对象都是通过单例模式create出来的,也就是说都使用了autorelease将自己放入自动化内存释放池中被管理。

比如我们在HelloCpp中看到过这样的代码:
复制代码 代码如下:
//HelloWorldScene.cpp
bool HelloWorld::init() {
     …. ….
    // add "HelloWorld" splash screen"
    CCSprite* pSprite = CCSprite::create("HelloWorld.png");

// position the sprite on the center of the screen
    pSprite->setPosition(ccp(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

// add the sprite as a child to this layer
    this->addChild(pSprite, 0);
    … …
}

CCSprite采用自动化内存管理模式create object(cocos2dx/sprite_nodes/CCSprite.cpp),之后将自己加入到HelloWorld这个CCLayer实例 中。按照上面的分析,create结束后,CCSprite object的m_uReference = 1; m_uAutoReleaseCount = 1。一旦如此,那么在下一帧时,该object就会被CCPoolManager释放掉。但我们在屏幕上依旧可以看到该Sprite的存在,这是怎么回事呢?

问题的关键就在this->addChild(pSprite, 0)这行代码中。addChild方法实现在CCLayer的父类CCNode中:
复制代码 代码如下:
//  cocos2dx/base_nodes/CCNode.cpp
void CCNode::addChild(CCNode *child, int zOrder, int tag)
{
    … …
    if( ! m_pChildren )
    {
        this->childrenAlloc();
    }

this->insertChild(child, zOrder);

… …
}

void CCNode::insertChild(CCNode* child, int z)
{
    m_bReorderChildDirty = true;
    ccArrayAppendObjectWithResize(m_pChildren->data, child);
    child->_setZOrder(z);
}

void ccArrayAppendObjectWithResize(ccArray *arr, CCObject* object)
{
    ccArrayEnsureExtraCapacity(arr, 1);
    ccArrayAppendObject(arr, object);
}

void ccArrayAppendObject(ccArray *arr, CCObject* object)
{
    CCAssert(object != NULL, "Invalid parameter!");
    object->retain();
    arr->arr[arr->num] = object;
    arr->num++;
}

又是一系列方法调用,最终我们来到了ccArrayAppendObject方法中,看到了陌生而又眼熟的retain方法调用。

在本文开始我们介绍CCObject时,我们知道retain是CCObject的一个方法,用于增加m_uReference计数。而实际上retain还隐含着“保留”这层意思。

在完成this->addChild(pSprite, 0)调用后,CSprite object的m_uReference = 2; m_uAutoReleaseCount = 1,这很关键。

我们在脑子里再过一下自动释放池释放object的过程:–m_uReference, –m_uAutoReleaseCount。一帧之后,两个值变成了m_uReference = 1; m_uAutoReleaseCount = 0。还记得前面说过的m_uAutoReleaseCount的另外一个非计数含义么,那就是表示该object是否“受控”,现在值为0,显然不再受自动释放池的控制了,后续即便再执行100次内存自动释放,也不会影响到该object的存活。

后续要想释放这个“精灵”,我们还是需要手工调用release,或再调用其autorelease方法。

时间: 2024-10-30 01:37:13

Cocos2d-x的内存管理总结的相关文章

cocos2d x-cocos2dx-cpp中的内存管理问题

问题描述 cocos2dx-cpp中的内存管理问题 请教一下:在GitHub中的cocos2dx-cpp中的cocos2d-x / tests / cpp-tests / Classes / AccelerometerTest / AccelerometerTest.cpp的代码`AccelerometerTestScene::runThisTest(){auto layer = new AccelerometerTest();addChild(layer);layer->release();/

从CCObject看cocos2d-x的内存管理机制

再看CCObject,剔除上节的拷贝相关,以及Lua脚本相关的属性和方法后,CCObject还剩下什么? 1.剩下什么? 可以看到整个CCObject就是围绕着m_uReference和m_uAutoReleaseCount在转.这两个变量的解释如下.所以CCObject剩下的其实就是对内存的管理. [cpp] view plaincopy CCObject::CCObject(void)//构造函数   : m_nLuaID(0)   , m_uReference(1) //引用计数,初始为1

JSB内存管理

原文地址:http://www.cocos2d-x.org/wiki/Memory_Management_of_JSB by u0u0 翻译:晋文格墨 JSB的内存管理 基于Cocos2d-x 2.15,但同样适用于Cocos2d-x 3.0. JSB对象的生命周期 总所周知,javascript有自己的内存管理机制,即垃圾回收.Cocos2d-x模拟垃圾回收系统来管理Cocos对象.但这里有一个问题,就是将Cocos2d-x对象绑定到javascript对象时由谁负责内存管理. 先看一个案例.

Cocos2d-x与ios内存管理分析(在游戏中减少内存压力)

  Cocos2d-x与ios内存管理分析(在游戏中减少内存压力) 猴子原创,欢迎转载.转载请注明: 转载自Cocos2D开发网--Cocos2Dev.com,谢谢!  原文地址: http://www.cocos2dev.com/?p=281 注:自己以前也写过Cocos2d-x如何优化内存的使用,以及内存不足的情况下怎么处理游戏.今天在微博中看到有朋友介绍了下内存,挺详细的.不知道是谁写的,我记录下. 一,iOS与图片内存 在iOS上,图片会被自动缩放到2的N次方大小.比如一张1024*10

聊聊内存管理

这篇文章我们聊聊内存管理. 本来我想不针对于任何具体的操作系统来谈内存管理,但是又觉得不接地气.言之无物.所以我决定在阐述概念的同时,还针对IA32平台Linux下的内存管理做简要的介绍,并且以实验来证明结论.以下内容分拆为几个大标题和小节,内容前后承接. 物理地址空间 首先,什么是物理地址空间?我们知道CPU与外部进行信息传递的公用通道就是总线,一般而言,CPU有三大总线:控制总线.数据总线.地址总线.这三类总线在一定程度上决定了CPU对外部设备的控制和数据传送能力.其中地址总线决定了CPU能

c#内存管理.

尽管在.net framework中我们不太需要关注内存管理和垃圾回收这方面的问题,但是出于提高我们应用程序性能的目的,在我们的脑子里还是需要有这方面的意识.明白内存管理的基本行为将有助于我们解释我们程序中变量是如何操作的.在本文中我将讨论栈和堆的一些基本知识,变量的类型和某些变量的工作原理. 当你在执行程序的时候内存中有两个地方用于存储程序变量.如果你还不知道,那么就来看看堆和栈的概念.堆和栈都是用于帮助我们程序运行的,包含某些特殊信息的操作系统内存模块.那么堆和栈有什么不同呢? 堆VS栈的区

iOS开发系列—Objective-C之内存管理

概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没有得到及时释放那么就会占用大量内存.其他高级语言如C#.Java都是通过垃圾回收来(GC)解决这个问题的,但在OjbC中并没有类似的垃圾回收机制,因此它的内存管理就需要由开发人员手动维护.今天将着重介绍ObjC内存管理: 引用计数器 属性参数 自动释放池 引用计数器 在Xcode4.2及之后的版本中

内存管理 之 存储器硬件知识

接下来,为了顺应Linux Kernel的学习,在操作系统方面首先学习的是内存管理.首先主要讲解物理内存的相关知识.本节主要讲解存储器的基础硬件知识,下一节讲解存储器的层次结构.   存储器是计算机系统的重要组成部分,它在计算机系统中的作用是存放程序和数据.存储器不仅使计算机具有记忆功能,而且是计算机高速自动运行的基础. 作为计算机的核心部件之一,存储器直接关系到整个计算机系统性能的高低.如何以合理的成本搭建出容量和速度都满足要求的存储器系统,始终是计算机体系结构设计中的关键问题之一:一方面,人

iOS ARC 内存管理要点

前言 在讨论 ARC 之前,我们需要知道 Objective-C 采用的是引用计数式的内存管理方式,这一方式的特点是: 自己生成的对象自己持有.比如:NSObject * __strong object = [NSObject alloc] init];. 非自己生成的对象自己也能持有.比如:NSMutableArray * __strong array = [NSMutableArray array];. 自己持有的对象不再需要时释放. 非自己持有的对象自己无法释放. 而 ARC 则是帮助我们

.NET中的内存管理,GC机制,内存释放过程

引言 作为一个.NET程序员,我们知道托管代码的内存管理是自动的..NET可以保证我们的托管程序在结束时全部释放,这为我们编程人员省去了不少麻烦,我们可以连想都不想怎么去管理内存,反正.NET自己会保证一切.好吧,有道理,有一定的道理.问题是,当我们用到非托管资源时.NET就不能自动管理了.这是因为非托管代码不受CLR(Common Language Runtime)控制,超出CLR的管理范围.那么如何处理这些非托管资源呢,.NET又是如何管理并释放托管资源的呢? 自动内存管理和GC 在原始程序