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://github.com/chukong/cocos-docs/blob/master/manual/framework/native/v3/memory-management/zh.md (内存管理机制)



【内存管理机制】

在3.x版本,Cocos2d-x采用全新的根类 Ref ,实现Cocos2d-x 类对象的引用计数记录。引擎中的所有类都派生自Ref。

1、引用计数

引用计数的概念参考《维基百科》:http://zh.wikipedia.org/wiki/引用计数

Cocos2d-x 提供引用计数管理内存。

> 调用 retain() 方法             :令其引用计数增1,表示获取该对象的引用权。

> 调用 release() 方法          :在引用结束的时候,令其引用计数值减1,表示释放该对象的引用权。

> 调用 autorelease() 方法 :将对象放入自动释放池。

> 当释放池自身被释放的时候,它就会对池中的所有对象执行一次release()方法,实现灵活的垃圾回收。

Cocos2d-x 提供 AutoreleasePool,管理自动释放对象。

> 当释放池自身被释放的时候,它就会对池中的所有对象执行一次release()方法。

核心类Ref:实现了引用计数。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

//

/**

 * CCRef.h

 **/

    class CC_DLL Ref

    {

        public:

            void retain();      // 保留。引用计数+1

            void release();     // 释放。引用计数-1

            Ref* autorelease(); // 实现自动释放。

            unsigned int getReferenceCount() const//被引用次数

        protected:

            Ref();          // 初始化

        public:

            virtual ~Ref(); // 析构

        protected:

            unsigned int _referenceCount; // 引用次数

            friend class AutoreleasePool; // 自动释放池

    };

/**

 * CCRef.cpp

 **/

    // 节点被创建时,引用次数为 1

    Ref::Ref() : _referenceCount(1)

    {

    }

    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)

        {

            delete this;

        }

    }

    Ref* Ref::autorelease()

    {

        // 将节点加入自动释放池

        PoolManager::getInstance()->getCurrentPool()->addObject(this);

        return this;

    }

//

Ref原理分析:

> 当一个 Ref 初始化(被new出来时),_referenceCount = 1;

> 当调用该 Ref 的 retain() 方法时,_referenceCount++;

> 当调用该 Ref 的 release() 方法时,_referenceCount--。

> 若  _referenceCount 减后为0,则 delete 该 Ref。

2、retain() 和 release() 使用

下面一段简单的例子来学习 retain() 和 release() 的使用。


1

2

3

4

5

6

7

8

9

10

11

12

//

    TestObject* obj1 = new TestObject("testobj1");

    CCLOG("obj1 referenceCount=%d",obj1->getReferenceCount());

    

    obj1->retain();

    CCLOG("obj1 referenceCount=%d",obj1->getReferenceCount());

    

    obj1->release();

    CCLOG("obj1 referenceCount=%d",obj1->getReferenceCount());

    

    obj1->release();

//

控制台显示的日志如下:


cocos2d: TestObject:testobj1 is created

cocos2d: obj1 referenceCount=1

cocos2d: obj1 referenceCount=2

cocos2d: obj1 referenceCount=1

cocos2d: TestObject:testobj1 is destroyed

通过例子和打印结果可以看到:

> obj1对象创建后,引用计数为1;

> 执行一次retain()后,引用计数为2;

> 执行一次release()后,引用计数回到1;

> 再执行一次release()后,对象会被释放掉。

因此:

> 我们可以调用retain()方法,令其引用计数增1,表示获取该对象的引用权;

> 在引用结束的时候调用release()方法,令其引用计数值减1,表示释放该对象的引用权。

> 直到对象的引用计数为0,释放该对象。

3、autorelease() 使用

同样一段简单的例子来学习autorelease的使用,代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//

    TestObject* obj = new TestObject("testobj");

    CCLOG("obj referenceCount=%d",obj->getReferenceCount());

    

    obj->autorelease();

    CCLOG("obj is add in currentpool %s",PoolManager::getInstance()->getCurrentPool()->contains(obj)?"true":"false");

    CCLOG("obj referenceCount=%d",obj->getReferenceCount());

    

    obj->retain();

    CCLOG("obj referenceCount=%d",obj->getReferenceCount());

    obj->release();

    CCLOG("obj referenceCount=%d",obj->getReferenceCount());

    //obj in current pool will be release

    Director::getInstance()->replaceScene(this);

//

控制台显示的日志如下:


cocos2d: TestObject:testobj is created

cocos2d: obj referenceCount=1

cocos2d: obj is add in currentpool true

cocos2d: obj referenceCount=1

cocos2d: obj referenceCount=2

cocos2d: obj referenceCount=1

...

cocos2d: TestObject:testobj is destroyed

通过代码和打印结果,我们可以看到:

> obj对象创建后,引用计数为1;

> 执行一次autorelease()后,obj对象被加入到当前的自动释放池。

> obj对象的引用计数值并没有减1。

> 但是在下一帧开始前,当前的自动释放池会被回收掉,并对自动释放池中的所有对象执行一次release()操作。

> 当对象的引用计数为0时,对象会被释放掉。

> obj对象执行autorelease()后,我们对其执行了一组retain()和release()操作。

> 此时obj对象的引用计数为1,在场景切换后,当前的自动释放池被回收,

> obj对象执行一次release()操作引用计数减为0时,对象会被释放掉。

注意:autorelease()只有在自动释放池被释放时才会进行一次释放操作,如果对象释放的次数超过了应有的次数,则这个错误在调用autorelease()时并不会被发现,只有当自动释放池被释放时(通常也就是游戏的每一帧结束时),游戏才会崩溃。在这种情况下,定位错误就变得十分困难了。

例如,在游戏中,一个对象含有1个引用计数,但是却被调用了两次autorelease()。在第二次调用autorelease()时,游戏会继续执行这一帧,结束游戏时才会崩溃,很难及时找到出错的地点。

    因此,我们建议在开发过程中应该避免滥用autorelease(),只在工厂方法等不得不用的情况下使用,尽量以release()来释放对象引用。

4、AutoreleasePool类 使用

Cocos2d-x提供AutoreleasePool,管理自动释放对象

下面一段简单的例子讲解AutoreleasePool的使用,代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//

    TestObject* obj2 = new TestObject("testobj2");

    CCLOG("obj2 referenceCount=%d",obj2->getReferenceCount());

    

    //use AutoreleasePool

    {

        AutoreleasePool pool;

    

        obj2->retain();

        CCLOG("obj2 referenceCount=%d",obj2->getReferenceCount());

    

        obj2->release();

        CCLOG("obj2 referenceCount=%d",obj2->getReferenceCount());

    

        obj2->autorelease();

        CCLOG("obj2 is add in pool %s",pool.contains(obj2)?"true":"false");

    

        TestObject *obj3 = new TestObject("testobj3");

        

        obj3->autorelease();

        CCLOG("obj3 is add in pool %s",pool.contains(obj3)?"true":"false");

    }

//

控制台输出日志如下:


cocos2d: TestObject:testobj2 is created

cocos2d: obj2 referenceCount=1

cocos2d: obj2 referenceCount=2

cocos2d: obj2 referenceCount=1

cocos2d: obj2 is add in pool true

cocos2d: TestObject:testobj3 is created

cocos2d: obj3 is add in pool true

cocos2d: TestObject:testobj2 is destroyed

cocos2d: TestObject:testobj3 is destroyed

通过代码和输出结果,可以看到:

> 创建了一个obj2对象,此时obj2对象的引用计数为1。

> 接着创建了一个自动释放池,对obj2对象执行retain()和release()操作后,执行autorelease()操作,此时obj2对象被加入到当前新建的自动释放池中。

> 接着新建了obj3对象,并执行autorelease()操作。同样obj3也被加入到当前新建的自动释放池中。

> 在代码块结束后,自动释放池被回收,加入自动释放池中的obj2和obj3执行release()操作,引用计数减为0,被释放销毁。

我们可以自己创建AutoreleasePool,管理对象的autorelease。

我们已经知道,调用了autorelease()方法的对象(下面简称"autorelease对象"),将会在自动释放池释放的时候被释放一次。虽然,Cocos2d-x已经保证每一帧结束后释放一次释放池,并在下一帧开始前创建一个新的释放池,但是我们也应该考虑到释放池本身维护着一个将要执行释放操作的对象列表,如果在一帧之内生成了大量的autorelease对象,将会导致释放池性能下降。因此,在生成autorelease对象密集的区域(通常是循环中)的前后,我们最好可以手动创建并释放一个回收池。

例如:


1

2

3

4

5

6

7

8

9

10

11

12

13

//

    // example of using temple autorelease pool

    {

        AutoreleasePool pool2;

        char name[20];

        for (int i = 0; i < 100; ++i)

        {

            snprintf(name, 20, "object%d", i);

            TestObject *tmpObj = new TestObject(name);

            tmpObj->autorelease();

        }

    }

//

总结:

> autorelease()的实质是将对象加入自动释放池,对象的引用计数不会立刻减1,在自动释放池被回收时对象执行release()。

> autorelease()并不是毫无代价的,其背后的释放池机制同样需要占用内存和CPU资源。

> 过多的使用autorelease()会增加自动释放池的管理和释放池维护对象存取释放的支出。

> 在内存和CPU资源本就不足的程序中使得系统资源更加紧张。

> 此时就需要我们合理创建自动释放池管理对象autorelease。

> 不用的对象推荐使用release()来释放对象引用,立即回收。

5、特殊内存管理

5.1、工厂方法 create()

在Cocos2d-x中,提供了大量的工厂方法创建对象。仔细看你会发现,这些对象都是自动释放的。

下面以 Label 的 create 方法为例,代码如下:


1

2

3

4

5

6

7

8

9

10

11

//

    Label* Label::create()

    {

        auto ret = new Label();

        if (ret)

        {

            ret->autorelease();

        }

        return ret;

    }

//

我们可以发现,创建了一个Label的对象,并对该对象执行autorelease()。表示该对象是自动释放的。细心的你会发放Layer/Scene/Sprite等类的 create() 方法都相同。

使用工厂方法创建对象时,虽然引用计数也为1,但是由于对象已经被放入了释放池,因此调用者没有对该对象的引用权,除非我们人为地调用了retain()来获取引用权,否则,不需要主动释放对象。

5.2、Node 的 addChild() / removeChild 方法

在Cocos2d-x中,所有继承自Node类,在调用 addChild 方法添加子节点时,自动调用了retain。 对应的通过 removeChild,移除子节点时,自动调用了release。

调用addChild方法添加子节点,节点对象执行retain。子节点被加入到节点容器中,父节点销毁时,会销毁节点容器释放子节点。对子节点执行release。如果想提前移除子节点我们可以调用removeChild。

在Cocos2d-x内存管理中,大部分情况下我们通过调用 addChild/removeChild 的方式自动完成了retain,release调用。不需再调用retain,release。



【内存优化】

1、内存优化原理

为优化应用内存使用,开发人员首先应该知什么最耗应用内存,答案就是纹理! 纹理几乎会占据90%应用内存。所以尽量最小化应用的纹理内存使用,否则应用很有可能会因为低内存而崩溃。

本节介绍Cocos2d-x游戏通用的两条内存优化原理指导

1.1、认识瓶颈寻找方案

什么样的纹理最耗应用内存?或这些纹理会消耗多少内存?

当然这个不用手动计算,只需猜测。工具在这里已经准备好了,使用的是苹果的工具“Allocation & Leaks”。你可以在Xcode中长按“Run”命令,选择“ Profile ”来启动这两个工具。

如下所示:

使用Allocation工具可以监控应用的内存使用,使用Leaks工具可以观察内存的泄漏情况。

此外还可用一些代码获取游戏内存使用的其他信息。

如下所示:


1

2

3

4

5

6

7

//

    Sprite* bg = Sprite::create("HelloWorld.png");

    bg->setPosition(240, 160);

    this->addChild(bg);

    CCLOG("%s", Director::getInstance()->getTextureCache()->getCachedTextureInfo().c_str());

//

调用这个代码后,游戏便会在DEBUG模式运行,这时你会在Xcode控制台窗口看到一些格式工整的日志信息。


1

2

3

4

5

//

    cocos2d: "****/HelloWorld.png" rc=2 id=3 480 x 320 @ 32 bpp => 600 KB

    "/cc_fps_images" rc=5 id=2 999 x 54 @ 16 bpp => 105 KB

    TextureCache dumpDebugInfo: 2 textures, for 705 KB (0.69 MB)

//

从上可以看到会显示纹理的名称、引用计数、ID、大小及每像素的位数。最重要的是会显示内存的使用情况。如“cc_fps_images”指消耗了105KB内存,而“HelloWorld.png”消耗了600KB内存。

1.2、切勿过度优化

这是一个通用的优化规则。在优化过程中,应该做一些权衡取舍。因为有时候图像质量和图像内存使用是处于两级的状态。千万不要过度优化!

2、内存优化水平

在此将ccos2d-x内存优化分为:三个等级

每个等级都有不同的说明,策略也有点不一样。

2.1、客户端等级

这是最重要的的优化等级。因为我们要在Cocos2d-x引擎顶层编译游戏,引擎自身会提供一些优化选项。 在这个等级我们可以进行大部分优化。简而言之,我们可以优化纹理、音频、字体及粒子的内存使用。

第一: 看纹理优化,为了优化纹理内存使用,必须知道什么因素对纹理内存使用的影响最大。主要有3个因素会影响纹理内存,即纹理格式(压缩还是非压缩)、颜色深度和大小。我们可以使用PVR格式纹理减少内存使用。推荐纹理格式为pvr.ccz。纹理使用的每种颜色位数越多,图像质量越好,但是越耗内存。所以我们可以使用颜色深度为RGB4444的纹理代替RGB8888,这样内存消耗会降低一半。此外超大的纹理也会导致内存相关问题。所以最好使用中等大小的纹理。

第二: 音频优化,3个因素会影响音频文件的内存使用,即音频文件数据格式、比特率及采样率。推荐使用MP3数据格式的音频文件,因为Android平台和iOS平台均支持MP3格式,此外MP3格式经过压缩和硬件加速。背景音乐文件大小应该低于800KB,最简单的方法就是减少背景音乐时间然后重复播放。音频文件采样率大约在96-128kbps为佳,比特率44kHz就够了。

第三:字体和粒子优化,在此有两条小提示:使用BMFont字体显示游戏分数时,请尽可能使用最少数量的文字。例如只想要显示单位数的数字,你可以移除所有字母。至于粒子,可以通过减少粒子数来降低内存使用。

2.2、引擎等级

需要 OpenGL ES 及游戏引擎高手。

2.3、C++语言等级

在这个等级中,建议是编写无内存泄露代码。遵循Cocos2d-x内置的内存管理原则,尽量避免内存泄露。

3.、提示和技巧

(1)    一帧一帧载入游戏资源

(2)    减少绘制调用,使用“Auto-batching”自动批处理。

(3)    载入纹理时按照从大到小的顺序

(4)    避免高峰内存使用

(5)    使用载入屏幕预载入游戏资源

(6)    需要时释放空闲资源

(7)    收到内存警告后释放缓存资源.

(8)    使用纹理打包器优化纹理大小、格式、颜色深度等

(9)    使用JPG格式要谨慎!

(10)  请使用RGB4444颜色深度16位纹理

(11)  请使用NPOT纹理,不要使用POT纹理

(12)  避免载入超大纹理

(13)  推荐1024*1024 NPOT pvr.ccz纹理集,而不要采用RAW PNG纹理

时间: 2024-07-29 18:55:18

cocos2dx[3.2](24)——内存管理机制的相关文章

cocos2d-x中的内存管理机制

cocos2d-x中的内存管理机制    Object *obj = new Object();        obj->autorelease();//自动释放资源        //    {    //        //在这段中使用obj    //    obj->retain();//保留对象    //    obj->release();//手动释放资源    //    //    }            log("Count %d",obj-&g

cocos2dx的内存管理机制

首先我们必须说一下c++中变量的内存空间的分配问题,我们在c++中写一个类,可以在栈上分配内存空间也可以使用new在堆上分配内存空间,如果类对象是在栈上分配的内存空间,这个内存空间的管理就不是我们的事了,但如果是在堆上分配的内存空间,当然需要我们来手动的delete了!cocos2dx采用的是在堆上分配内存空间,想想看你在写程序的时候对于cocos2dx中的类是不是大多数都是通过工厂方法获得的一个指针,你见过在栈上分配内存空间的情况吗?所以问题来了,既然在堆上分配内存空间,那么如何管理这个内存空

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来分配内存.通过垃圾回收机制来回收. (详

Cocos2d-x 3.1 内存管理机制

Cocos2d-x使用的内存管理方式是引用计数,引用计数是一种很有效的机制,通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数.当对象增加一次引用时,计数器加1:而对象失去一次引用时,计数器减1:当引用计数为0时,标志着该对象的生命周期结束,自动触发对象的回收释放.引用计数的重要规则是每一个程序片段必须负责任地维护引用计数,在需要维持对象生存的程序段的开始和结束分别增加和减少一次引用计数,这样就可以实现十分灵活的内存管理. 接下来看一下Cocos2d-x 3.1 版本的源码是怎么实现引

帝国塔防2充值界面闪退BUG修复总结(cocos2dx内存管理机制)

游戏充值界面老是闪断,debug调试断点总是断在比较深的位置,也看不出哪里出错. 后来有一次断点断在了程序创建一个提示图片的地方,于是检查代码发现了问题. 修改之前的错误代码: 1.创建提示的代码: m_loading = new NetLoading(this, callfunc_selector(IapShopLayer::downingUpdate)); // m_loading 引用计数为1 m_loading->setPosition(ccp(1139/2.0f, 640.0f/2.0

2、COCOS2D-X内存管理机制

在C++中,动态内存分配是一把双刃剑,一方面,直接访问内存地址提高了应用程序的性能,与使用内存的灵活性:另一方面,由于程序没有正确地分配与释放造成的例如野指针,重复释放,内存泄漏等问题又严重影响着应用程序的稳定性. 人们尝试着不同的方案去避免这个问题,比较常用的如智能指针,自动垃圾回收等,这些要么影响了应用程序的性能,要么仍然需要依赖于开发者注意一些规则,要么给开发者带来了另外一些很丑陋的用法(实际上笔者很不喜欢智能指针).因此,优秀的C++内存管理方案需要兼顾性能,易用性,所以到目前为止C++

内存管理机制

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

Cocos2d-x开发中C++内存管理

由于开始并没有介绍C++语言,C++的内存管理当然也没进行任何的说明,为了掌握Cocos2d-x中的内存管理机制,是有必要先了解一些C++内存管理的知识.C++内存管理非常复杂,如果完全地系统地介绍可能需要一本书的篇幅才能解释清楚.这里只给大家介绍C++内存管理最为基本的用法. 内存分配区域创建对象需要两个步骤:第一步,为对象分配内存,第二步,调用构造函数初始化内存.在第一步中对象分配内存时候,我们可以选择几个不同的分配区域,这几个区域如下:栈区域分配.栈内存分配运算内置于处理器的指令集中,效率