Cocos2d-x 3.1 内存管理机制

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 内存管理机制

时间: 2024-10-06 09:20:47

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

Cocos2d之&ldquo;引用计数&rdquo;内存管理机制实现解析

一.引言 本文主要分析cocos2d游戏开发引擎的引用计数内存管理技术的实现原理.建议读者在阅读本文之前阅读笔者之前一篇介绍如何使用cocos2d内存管理技术的文章--<Cocos2d之Ref类与内存管理使用详解>. 二.相关概念 引用计数 引用计数是计算机编程语言的一种内存管理技术,是指将资源(对象.内存或者磁盘空间等)的被引用计数保存起来,当引用计数变为零时就将资源释放的过程.使用引用计数技术可以实现自动内存管理的目的. 当实例化一个类时,对象的引用计数为1,在其他对象需要持有这个对象时,

cocos2d 内存管理机制

简单做下笔记,等有更深的理解时再补充. Cocos2d内存管理的基本原理是对象内存引用计数.当声明定义一个对象时,会在堆上为这个对象分配内存,并且有一个变量m_uReference专门用于记录该对象被引用了多少次. 内存引用计数的原理就是,当该对象被引用时m_uReference++,当该对象被取消引用时m_uReference--,若果m_uReference==0时,该对象就会被释放. 有了基本概念后,就不得不提及两个重要的函数--retain()和release().这两个函数是干什么用的

cocos2dx[3.2](24)——内存管理机制

[参考] http://zh.wikipedia.org/wiki/引用计数 (引用计数--维基百科) http://cn.cocos2d-x.org/tutorial/show?id=2300 (引用计数和自动释放池) http://cn.cocos2d-x.org/tutorial/show?id=1331 (内存管理--绕不过去的坎) http://blog.csdn.net/legendof1991/article/details/23360131 (内存优化) https://gith

Cocos2d-x学习笔记—内存管理机制

Cocos2d-x 3.x内存管理机制 1:C++内存管理 1-1:内存分配区域 创建对象需要两个步骤:第一步,为对象分配内存:第二步,调用构造函数初始化内存.在第一步中,可以选择几个不同的分配区域.这几个区域如下: (1) 栈区域分配.栈内存分配运算内置于处理器的指令集中,效率很髙,但是分配的内 存容量有限.由处理器自动分配和释放,用来存放函数的参数值和局部变量的值等.在执 行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放. (2) 堆区域分配.从堆上分

cocos2d-x 3.0 内存管理机制

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 再来一弹,内存管理机制 1.简言机制 2.代码观机制 1.简言-Cocos2d-x的内存管理机制 说到内存管理,这是个question,(⊙v⊙)嗯.. 看一下各语言怎么进行内存管理吧? --JAVA: 堆区的就是new来分配内存.通过垃圾回收机制来回收. (详

内存管理机制

Objective-C中提供了两种内存管理机制MRC(MannulReference Counting)和ARC(Automatic Reference Counting),分别提供对内存的手动和自动管理,来满足不同的需求. ARC: ARC是Auto Reference Counting的缩写,即自动引用计数,由编译器在代码合适的位置中自动添加retain/Release/Autorelease/dealloc方法从而进行内存管理. ARC几个要点: 在对象被创建时 retain count

Linux内存管理机制

一.首先大概了解一下计算机CPU.Cache.内存.硬盘之间的关系及区别. 1.  CPU也称为中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路, 是一台计算机的运算核心(Core)和控制核心( Control Unit).它的功能主要是解释计算机指令以及处理计算机软件中的数据.中央处理器主要由三核心部件组成,运算器.控制器和总线(BUS),运算器又主要由算术逻辑单元(ALU)和寄存器(RS)组成. 2.Cache即高速缓冲存储器,是位于CPU与主内存

iOS内存管理机制

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

32位机内存管理机制(上)

一直有看linux内核的冲动,内核有些部分是汇编编写的,无奈汇编不大懂,所以利用五一三天假期大概走了一边8086CPU架构的汇编,8086CPU还是16位的,我们现在都进入64位时代了,这两者之间有很大的区别,但是看看16位的CPU汇编还是很重要的,这有助于理解32位的80386CPU.这篇文章来分析下80386的内存管理的一些基础知识,包括实模式.保护模式和内存寻址等等. 1.实模式 处理器被复位或者加电的时候以实模式启动.这时候处理器中各寄存器以实模式的初始化值工作. 80386处理器在实模

轻量级操作系统FreeRTOS的内存管理机制(三)

本文由嵌入式企鹅圈原创团队成员朱衡德(Hunter_Zhu)供稿. 轻量级操作系统FreeRTOS的内存管理机制(二)中讲到,heap2.c的内存管理机制会导致内存碎片的问题,系统运行久后会出现无法分配大块内存的情况,heap4.c中的管理机制提供了解决方法,它是在heap2.c的基础上添加了地址相邻空闲块间合并的功能,而heap5.c是对heap4.c的进一步扩展,它能够支持多块不连续分布的RAM空间作为堆使用,本篇将对heap4.c.heap5.c中的管理机制进行分析. 一.heap4.c