思路:少申请内存,优化资源使得总体更少的内存,高效的利用和管理内存,内存还需要常驻内存不需要马上释放。
一、内存的申请
频繁申请的内存:因为堆内存申请比较耗时,对于频繁需要申请内存的对象,用内存池获取,避免每次向操作系统申请堆资源,可以有效的提高申请内存的时间。
非频繁的申请内存:采用传统的malloc,new的方式申请内存,不用了马上释放。
一次尽量申请更多的内存,而不是多次频繁申请小内存:
CCTextureCache、 CCSpriteFrameCache、CCAnimationCache;都可以为其添加一个key,后面通过key索引获取纹理、精灵或动画。
二、优化资源总体使用更少的内存
资源占用的内存优化
对纹理资源进行优化。
1).图片分类合并: 成plist大图文件减少内存,常驻的plist和公共的plist可以进入游戏就加载;特定界面的plist可以打开时候加载,如果太大卡顿那么进入场景时候加载,离开场景时候释放。因为Opengl是以2的整数幂申请图片纹理内存的,所以用plist可有效节省内存。
2).图片压缩: 质量要求不高的RGBA8888,压缩为RGBA4444加上texturePacket的插值算法; 不要求Alpha通道的用TP去掉alpha通道,采用RGB565格式,图片大小也会减半。
3).图片格式:android一般用png格式, ios用pvr格式,PC用tga格式,图片更小,加载速度更快。不要是个jpg格式的图片!
4)减少图片的量:如果是对称的图片,那么需要一个就可以了,另一个进行旋转得到;如果是相同的一个小图片的放缩,那么不需要一整个大图,只需要小图进行九宫格放大。
公共的图片资源放置到一个公共的地方,不用每个界面都存一份,且这些公共的图片可以常驻在内存中。
三、内存飙升的控制
使用预加载和延后加载:预加载可以有效的控制内存飙升,延后加载例如进入一个地图场景先把当前传送点的地图加载出来,其它的地图资源可以后面异步的加载出来。
将资源载入内存的顺序:按照纹理size从大到小的顺序加载纹理。避免在收到内存警告消息的时候清除缓存。
四、高效的利用和管理内存
高效利用:更好的利用指针和引用的优势,尽量在内存里面一份数据只占用一份内存空间,其它的地方取得数据都通过指针或引用的方式。但是除了本模块保存内存对象索引的指针,其它模块不应该去保存其它模块的指针除非用了引用计数的内存管理,否则很容易出现野指针而崩溃;这样其它模块只能另外保存一份小副本或者每次使用都要查找对象(log2n的查找其实也是很快的)。
有效的管理内存:使用内存池和引用计数的方案:
游戏启动的时候就分配很大的一块区域block,游戏运行过程中不释放,直到退出游戏再释放。平时不是为新资源分配内存,而是把资源加载到内存池里面。多次使用的时候,就不用经过多次的释放和重新加载。这部分可以多了解下C++的内存池管理机制。
STL,MFC,BOOST,cocos2d都是有自己的内存池管理的,一般设计是一个pool对象管理自由多的free list链表(分段式的链表),每个free list链表大块内存叫chunk,chunk块内有小块的内存块叫block( boost刚相反,大块叫block,小块叫chunk)。申请内存策略是“先申请一块大的chunk,然后需要时候慢慢切割”切割后组织成小块的block便于索引。如果不使用那么可以标记这些chunk,block是free的,可以再使用。
原则:权衡内存池是用内存空间代价换取的高时间性能,所以只有频繁申请内存的地方才采用内存池获取内存,非频繁申请的可以用传统的方式获取内存,不用的时候马上删掉内存。
五、内存的释放
内存池中使用的内存,不用的时候应该马上归还给内存池,标记为free的,方便下次使用,不至于内存池不断增大。
非内存池中的内存,应该使用完了以后马上释放。
内存泄露的需要注意:
1)申请和释放需要一一对应。
2)结构体指向的内存都要释放到。
3)父类内存需要虚析构函数保证释放。
4)函数返回内存指针隐形的内存也要释放到。
5)整个程序的释放时机,智能指针的好处:整个程序中要注意,free,delete[],delete的时机也会导致问题的出现。
6)STL容器在类析构函数中需要显式的clear保证内存的释放。
例如在:cocos2dx中:
对于缓存纹理,精灵和动画类,在场景切换时我们应该加入如下的清理缓存操作:
voidreleaseCaches()
{
CCAnimationCache::purgeSharedAnimationCache();
CCSpriteFrameCache::sharedSpriteFrameCache()->removeUnusedSpriteFrames();
CCTextureCache::sharedTextureCache()->removeUnuserdTextures();
}
值得注意的是清理的顺序,应该先清理动画缓存,然后清理精灵帧,最后是纹理。按照引用层级由高到低,以保证保释引用有效。