【cocos2d-x 3.x 学习笔记】对象内存管理

内存管理

内存管理一直是一个不易处理的问题,开发者必须考虑分配回收的方式和时机,针对堆和栈做不同的优化处理,等等。内存管理的核心是动态分配的对象必须保证在使用完毕后有效地释放内存,即管理对象的生命周期。由于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 源代码

时间: 2024-10-29 19:10:01

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

linux kernel学习笔记-5内存管理(转)

http://blog.sina.com.cn/s/blog_65373f1401019dtz.htmllinux kernel学习笔记-5 内存管理1. 相关的数据结构 相比用户空间而言,在内核中分配内存往往受到更多的限制,比如内核中很多情况下不能睡眠,此外处理内存分配失败也不像用户空间那么容易.内核使用了页和区两种数据结构来管理内存: 1.1 页 内核把物理页作为内存管理的基本单位.尽管CPU的最小可寻址单位通常为字(甚至字节),但是MMU(内存管理单元,管理内存并把虚拟地址转换为物理地址的

Linux System Programming 学习笔记(九) 内存管理

1. 进程地址空间 Linux中,进程并不是直接操作物理内存地址,而是每个进程关联一个虚拟地址空间 内存页是memory management unit (MMU) 可以管理的最小地址单元 机器的体系结构决定了内存页大小,32位系统通常是 4KB, 64位系统通常是 8KB 内存页分为 valid or invalid: A valid page is associated with an actual page of data,例如RAM或者磁盘上的文件 An invalid page is

ios学习笔记之内存管理

一,内存管理类型定义      1,基本类型  任何C的类型,eg:      int,short,char,long,long long,struct,enum,union等属于基本类型或结构体      内存管理对于C语言的基本类型无效      2,OC类型(非基本类型)      任何继承于NSObject类的对象都属于OC类型      也就是除了C之外的其他类型 二,OC对象结构      所有OC对象都有着一个计数器,保留着当前对象被引用的数量,如果计数器为0,那么就真正的释放这个

《C#高级编程》学习笔记----c#内存管理

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

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

一 页 内核把物理页作为内存管理的基本单位:内存管理单元(MMU)把虚拟地址转换为物理 地址,通常以页为单位进行处理.MMU以页大小为单位来管理系统中的也表. 32位系统:页大小4KB 64位系统:页大小8KB 内核用相应的数据结构表示系统中的每个物理页: <linux/mm_types.h> struct page {} 内核通过这样的数据结构管理系统中所有的页,因此内核判断一个页是否空闲,谁有拥有这个页 ,拥有者可能是:用户空间进程.动态分配的内核数据.静态内核代码.页高速缓存…… 系统中

iOS学习笔记之内存管理及@property

iOS5之后苹果公司引入了ARC机制,大大方便了ios开发者对内存的管理机制.在iphone 4出世的时候为什么ios在512M的内存中可以运行很大的游戏,保持畅快流畅的状态.得益于ios非常好的内存处理机制. 在我们现在创建项目的时候,默认会直接引入ARC机制,我们可以关闭ARC机制:在输入框中输入long点击搜索按钮,如图: 接下来即可进行老版本的内存操作了. 在老版本中,内存操作采用了引用计数(retainCount)alloc retain(+1)release(-1) 内存管理原则(配

黑马程序员_IOS开发_Objective-C学习笔记_内存管理

1.内存管理概述 1.1什么是内存管理:内存管理是程序设计中常用的资源管理的一部分,每个计算机系统可供程序使用的内存都是有限的. 1.2为什么要使用内存管理:当我们的程序运行结束的时候,操作系统将回收其我们程序占用内存.但是,只要程序还在运行,它就会一直占用内存.如果不进行及时清理不用的内存,内存最终将被耗尽.每个程序都会使用内存,我们必须确保在需要的时候分配内存,而在程序运行结束时释放占用的内存.如果我们只分配而不释放内存,将发生内存泄漏. 1.3引用计数:1.3.1只有当你对一个对象了all

Objective-C学习笔记_内存管理(二)

一.属性的内部实现原理 assign的属性内部实现 setter方法: // setter方法 @property (nonatomic, assign) NSString *name; - (void)setName:(NSString *)name { _name = name; } getter方法: // getter方法 - (NSString *)name { return _name; } 观察下面代码会出现什么问题? NSString *name = [[NSString all

Objective-C学习笔记_内存管理(一)

一.内存管理的?式 大家都去过图书馆,而图书馆里的书是可以借出的.我们来设想这样一个场景,大家都去借书,但是从来没有人去还书,那么最后,这个图书馆会因为无书可借而倒闭,每个人都没法再使用图书馆.计算机也是这样,当程序运行结束时,操作系统将回收其占用的资源.但是,只要程序运行就会占用资源,如果不进行清理已经不用的资源,资源最终将被耗尽,程序将崩溃. 学会内存管理我们就明白什么时候由你释放对象,什么时候你不能释放.C语言中通过malloc.calloc.realloc和free搭配对内存进行管理.但

计算机操作系统学习笔记_7_内存管理 --内存管理基础

h2.western { font-family: "Liberation Sans",sans-serif; font-size: 16pt; }h2.cjk { font-family: "微软雅黑"; font-size: 16pt; }h2.ctl { font-family: "AR PL UMing CN"; font-size: 16pt; }h1 { margin-bottom: 0.21cm; }h1.western { fon