内存管理

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

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

  1. 处理内存管理和iOS Cordova项目?

    任何人都可以告诉我如何处理基于iOSCordova的项目中的内存管理“收到内存警告”当我在iPhone或iPad上运行时,我在iOSCordova项目中收到此警告.我在我的应用程序中使用CDVlocation进行地理定位.我主要是在加载基于地图的视图时收到此消息.我正在使用基于ARC的Xcode项目任何帮助管理内存警告与cordova“收到内存警告”将不胜感激.谢谢你们解决方法在CDVPlugin.m中尝试这种方式

  2. xcode – Swift 1.2中的神秘崩溃 – 仅在版本中构建

    我的理论是,Swift的内存管理在优化的版本编译中出现了一些问题,只是在NSArray从Cocoa到达的特定情况下,在我们的代码可以获得它们之前,我们将它们桥接到[AnyObject].这样的NSArray没有正确地穿过桥.但是通过转换到NSArray然后回到特定的[SomeType]Swift数组,问题就解决了.当然,我认为当苹果公司指出这一点时,他们会解决这个问题,然后我们可以停止使用这种解决方法.但直到那时,我的应用程序再次在Release版本中运行.

  3. Xamarin.iOS在将C#编译为本机代码时对内存管理做了什么?

    什么Xamarin.iOS关于内存管理?使用通常的IL,我们有垃圾收集器,它处理未使用的对象和浮雕程序员调用删除.当Xamarin将代码编译为本机时,这是如何工作的?谁清理了不再使用的物体?这个问题回答了编译的工作原理,但没有解释内存管理部分:HowMonoTouchworks?

  4. swift语言的学习笔记六(ARC-自动引用计数,内存管理)

    Swift使用自动引用计数来管理应用程序的内存使用。当实例并不再被需要时,ARC会自动释放这些实例所使用的内存。swift的ARC工作过程每当创建一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息。但是,如果ARC释放了正在被使用的实例,就不能再访问实例属性,或者调用实例的方法了。为了保证需要实例时实例是存在的,ARC对每个类实例,都追踪有多少属性、常量、变量指向这些实例。

  5. Swift内存管理概述

    Object-C的内存管理经历过2个阶段:手动引用计数内存管理MRC和自动引用计数内存引用管理ARC-MRC就是由程序员自己负责对象管理生命周期,负责对象的创建和销毁,ARC的程序员不用关心对象内存释放的问题,编译器在编译的时候在合适的代码位置插入内存释放代码。以C++和C为代表的手动内存管理模式,使用起来比较麻烦,经常导致内存泄露和内存过度释放等问题。

  6. Swift ARC-自动引用计数、内存管理

    当实例并不再被需要时,ARC会自动释放这些实例所使用的内存。测试ARC输出结果从上面的例子来看,确实swift给我们自动管理了内存,很多时侯开发者都不需要考虑太多的内存管理。尽管ARC减少了很多内存管理工作,但ARC并不是绝对安全的。但是当无宿主引用所指实例被释放时,ARC并不能将引用值设置为nil,因为非可选类型不能设置为nil。

  7. 关东升的《Swift2.0》暂定名即将出版

    关东升的《Swift2.0》(暂定名)即将出版大家好:苹果2015WWDC大会发布了Swift2.0,它较之前的版本Swift1.x有很大的变化,所以我将在11月左右出版,《Swift2.0》(暂定名)Swift2.0》将在《Swift开发指南》第1版的基础上添加Swift2.0的内容,同时摒弃第1版的一些不合理的内容,使得本书更加适合Swift开发者。我将连续发一些Swift2.0的学习笔记,有兴趣者,可以看一下。

  8. 初探swift语言的学习笔记六(ARC-自动引用计数,内存管理)

    Swift使用自动引用计数来管理应用程序的内存使用。当实例并不再被需要时,ARC会自动释放这些实例所使用的内存。swift的ARC工作过程每当创建一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息。但是,如果ARC释放了正在被使用的实例,就不能再访问实例属性,或者调用实例的方法了。为了保证需要实例时实例是存在的,ARC对每个类实例,都追踪有多少属性、常量、变量指向这些实例。

  9. swift 内存管理

    Swift是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配。当我们通过初始化创建一个对象时,Swift会替我们管理和分配内存。添加weak后的输出:AdeinitBdeinit可能有心的朋友已经注意到,在Swift中除了weak以外,还有另一个冲着编译器叫喊着类似的“不要引用我”的标识符,那就是uNowned。可以将printName修改为这样:lazyvarprintName:()->()={[weakself]inifletstrongSelf=self{println(“Thename

  10. Swift内存管理ARC之循环引用

    此时这个Cat实例对象已经有三个强引用了。只有直到对象所有的引用被打破,ARC才会释放该对象当最后一个强引用被打破后,这个Cat对象被成功释放,输出:jack对象释放成功!解决循环引用办法Swift提供了两种方法来解决循环引用问题:弱引用无主引用声明变量或属性时,在前面加上”weak”关键词表示它是一个弱引用。从而达到打破循环引用的效果。这样便导致了循环引用。

随机推荐

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

    Cocos2d-x的内存管理cocos2d-x中使用的是上面的引用计数来管理内存,但是又增加了一些自己的特色。cocos2d-x中通过Ref类来实现引用计数,所有需要实现内存自动回收的类都应该继承自Ref类。下面是Ref类的定义:在cocos2d-x中创建对象通常有两种方式:这两中方式的差异可以参见我另一篇博文“对象创建方式讨论”。在cocos2d-x中提倡使用第二种方式,为了避免误用第一种方式,一般将构造函数设为protected或private。参考资料:[1]cocos2d-x高级开发教程2.3节[

  2. 利用cocos2dx 3.2开发消灭星星六如何在cocos2dx中显示中文

    由于编码的不同,在cocos2dx中的Label控件中如果放入中文字,往往会出现乱码。为了方便使用,我把这个从文档中获取中文字的方法放在一个头文件里面Chinese.h这里的tex_vec是cocos2dx提供的一个保存文档内容的一个容器。这里给出ChineseWords,xml的格式再看看ChineseWord的实现Chinese.cpp就这样,以后在需要用到中文字的地方,就先include这个头文件然后调用ChineseWord函数,获取一串中文字符串。

  3. 利用cocos2dx 3.2开发消灭星星七关于星星的算法

    在前面,我们已经在GameLayer中利用随机数初始化了一个StarMatrix,如果还不知道怎么创建星星矩阵请回去看看而且我们也讲了整个游戏的触摸事件的派发了。

  4. cocos2dx3.x 新手打包APK注意事项!

    这个在编译的时候就可以发现了比较好弄这只是我遇到的,其他的以后遇到再补充吧。。。以前被这两个问题坑了好久

  5. 利用cocos2dx 3.2开发消灭星星八游戏的结束判断与数据控制

    如果你看完之前的,那么你基本已经拥有一个消灭星星游戏的雏形。开始把剩下的两两互不相连的星星消去。那么如何判断是GameOver还是进入下一关呢。。其实游戏数据贯穿整个游戏,包括星星消除的时候要加到获得分数上,消去剩下两两不相连的星星的时候的加分政策等,因此如果前面没有做这一块的,最好回去搞一搞。

  6. 利用cocos2dx 3.2开发消灭星星九为游戏添加一些特效

    needClear是一个flag,当游戏判断不能再继续后,这个flag变为true,开始消除剩下的星星clearSumTime是一个累加器ONE_CLEAR_TIME就是每颗星星消除的时间2.连击加分信息一般消除一次星星都会有连击信息和加多少分的信息。其实这些combo标签就是一张图片,也是通过控制其属性或者runAction来实现。源码ComboEffect.hComboEffect.cpp4.消除星星粒子效果消除星星时,为了实现星星爆裂散落的效果,使用了cocos2d提供的粒子特效引擎对于粒子特效不了

  7. 02 Cocos2D-x引擎win7环境搭建及创建项目

    官网有搭建的文章,直接转载记录。环境搭建:本文介绍如何搭建Cocos2d-x3.2版本的开发环境。项目创建:一、通过命令创建项目前面搭建好环境后,怎样创建自己的Cocos2d-x项目呢?先来看看Cocos2d-x3.2的目录吧这就是Cocos2d-x3.2的目录。输入cocosnew项目名–p包名–lcpp–d路径回车就创建成功了例如:成功后,找到这个项目打开proj.win32目录下的Hello.slnF5成功了。

  8. 利用cocos2dx 3.2开发消灭星星十为游戏添加音效项目源码分享

    一个游戏,声音也是非常的重要,其实cocos2dx里面的简单音效引擎的使用是非常简单的。我这里只不过是用一个类对所有的音效进行管理罢了。Audio.hAudio.cpp好了,本系列教程到此结束,第一次写教程如有不对请见谅或指教,谢谢大家。最后附上整个项目的源代码点击打开链接

  9. 03 Helloworld

    程序都有一个入口点,在C++就是main函数了,打开main.cpp,代码如下:123456789101112131415161718#include"main.h"#include"AppDelegate.h"#include"cocos2d.h"USING_NS_CC;intAPIENTRY_tWinMain{UNREFERENCED_ParaMETER;UNREFERENCED_ParaMETER;//createtheapplicationinstanceAppDelegateapp;return

  10. MenuItemImage*图标菜单创建注意事项

    学习cocos2dx,看的是cocos2d-x3.x手游开发实例详解,这本书错误一大把,本着探索求知勇于发现错误改正错误的精神,我跟着书上的例子一起调试,当学习到场景切换这个小节的时候,出了个错误,卡了我好几个小时。

返回
顶部