zoukankan      html  css  js  c++  java
  • 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 所看到的。



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

    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    macOS Sierra 如何打开任何来源
    centos 安装git服务器,配置使用证书登录并你用hook实现代码自动部署
    Linux下修改Mysql的用户(root)的密码
    mysql主从复制
    CentOS7下安装MySQL5.7安装与配置
    gulp安装和使用
    libiconv库的安装和使用
    Android 开发中常见的注意点
    扯一扯 C#委托和事件?策略模式?接口回调?
    Python 学习开篇
  • 原文地址:https://www.cnblogs.com/yxwkf/p/4675819.html
Copyright © 2011-2022 走看看