cocos2dx 学习(-)内存管理机制

2024-06-05 12:48

本文主要是介绍cocos2dx 学习(-)内存管理机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://blog.csdn.net/xzben/article/details/41979601

一、题记

         关于cocos2dx 的内存管理机制,想必大家都能清楚说出是通过引用计数(Reference Count)和自动释放池(AutoReleasePool)。但是不知大家是否知道其中具体的运行的细节呢?反正在写这篇blog之前我是一知半解的,而且在粗略的看了下 PoolManager 的源码时我还开始怀疑过这个机制的可靠性,于是我还专门找了测内存泄漏的工具 vs2010使用vld检测内存泄露,一测发现项目还真存在内存泄漏的问题。可是后来发现原来是自己用的别人的敏感字过滤开源库(https://github.com/xjdrew/crab)存在问题。经我改造后(https://github.com/xzben/GameFrame/tree/master/client/GameClient/frameworks/runtime-src/Classes/crab) 的版本修复此问题,并调整了api的结果。可是修复了我自己的代码的内存泄漏的问题后,发现 Cocos2dx 真的有内存泄漏(哈哈终于抓到尾巴了内心窃喜)。可是发现原来只是cocos2dx 一个单例模式的对象忘记了释放而已(这里我想吐槽cocos2dx 单例模式的实现,这里可以参考我之前写的一篇关于单例模式实现方式的博文http://blog.csdn.net/xzben/article/details/17080757#t14),并不是我想得PoolManager 导致的。终于发现原来cocos2dx 的内存管理模式其实应该 是  引用计数(Reference Count)和自动释放池(AutoReleasePool)和 内置CCVector和其它Cocos2dx内置容器类辅助实现的。下面来自己分析一下。


1、首先要说说我为什么怀疑它的可靠性。

因为我发现cocos2dx 的 AutoReleasePool 其实是通过 PoolManager  在每一帧调用一次clear

[cpp] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void DisplayLinkDirector::mainLoop()  
  2. {  
  3.     if (_purgeDirectorInNextLoop)  
  4.     {  
  5.         _purgeDirectorInNextLoop = false;  
  6.         purgeDirector();  
  7.     }  
  8.     else if (! _invalid)  
  9.     {  
  10.         drawScene();  
  11.        
  12.         // release the objects  
  13.         PoolManager::getInstance()->getCurrentPool()->clear();  
  14.     }  
  15. }  

[cpp] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void AutoreleasePool::clear()  
  2. {  
  3. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)  
  4.     _isClearing = true;  
  5. #endif  
  6.     for (const auto &obj : _managedObjectArray)  
  7.     {  
  8.         obj->release();  
  9.     }  
  10.     _managedObjectArray.clear();  
  11. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)  
  12.     _isClearing = false;  
  13. #endif  
  14. }  
仔细看了一下clear的实现后发现一个漏洞,那就是 它在将 _managedObjectArray 中的obj relase 之后就将 _managedObjectArray clear 了,那那些 release之后 引用计数还是大于0的谁来管理呢?我查看了 Node, Ref 的实现中,都没有找到对这些obj 管理,原先使用过程中我们知道 addChild 后就不怕被 PoolManager 给释放了。但是我在addChild中也没看到 retain 在 removeFromParent 也没看到 release 的操作,于是我就产生了疑问。

2、如何快速找到问题的关键的。

还好我在上一家公司学会了vs的各种调试技巧(用数据断点的方式查看 Ref 的引用计数变量的变化),让我一下子就找到了问题的所在。原来这个管理工作隐藏在了 内置容器 Vector 中了。

[cpp] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void pushBack(T object)  
  2.  {  
  3.      CCASSERT(object != nullptr, "The object should not be nullptr");  
  4.      _data.push_back( object );  
  5.      object->retain();  
  6.  }  
  7.    
[cpp] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void popBack()  
  2. {  
  3.     CCASSERT(!_data.empty(), "no objects added");  
  4.     auto last = _data.back();  
  5.     _data.pop_back();  
  6.     last->release();  
  7. }  

3、隐藏的一个危险,不知道大家有没有注意到 Ref 里面一个Debug才会运行的代码

[cpp] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void Ref::release()  
  2. {  
  3.     CCASSERT(_referenceCount > 0, "reference count should greater than 0");  
  4.     --_referenceCount;  
  5.   
  6.     if (_referenceCount == 0)  
  7.     {  
  8. #if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)  
  9.         auto poolManager = PoolManager::getInstance();  
  10.         if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))  
  11.         {  
  12.             // Trigger an assert if the reference count is 0 but the Ref is still in autorelease pool.  
  13.             // This happens when 'autorelease/release' were not used in pairs with 'new/retain'.  
  14.             //  
  15.             // Wrong usage (1):  
  16.             //  
  17.             // auto obj = Node::create();   // Ref = 1, but it's an autorelease Ref which means it was in the autorelease pool.  
  18.             // obj->autorelease();   // Wrong: If you wish to invoke autorelease several times, you should retain `obj` first.  
  19.             //  
  20.             // Wrong usage (2):  
  21.             //  
  22.             // auto obj = Node::create();  
  23.             // obj->release();   // Wrong: obj is an autorelease Ref, it will be released when clearing current pool.  
  24.             //  
  25.             // Correct usage (1):  
  26.             //  
  27.             // auto obj = Node::create();  
  28.             //                     |-   new Node();     // `new` is the pair of the `autorelease` of next line  
  29.             //                     |-   autorelease();  // The pair of `new Node`.  
  30.             //  
  31.             // obj->retain();  
  32.             // obj->autorelease();  // This `autorelease` is the pair of `retain` of previous line.  
  33.             //  
  34.             // Correct usage (2):  
  35.             //  
  36.             // auto obj = Node::create();  
  37.             // obj->retain();  
  38.             // obj->release();   // This `release` is the pair of `retain` of previous line.  
  39.             CCASSERT(false"The reference shouldn't be 0 because it is still in autorelease pool.");  
  40.         }  
  41. #endif  
  42.   
  43. #if CC_USE_MEM_LEAK_DETECTION  
  44.         untrackRef(this);  
  45. #endif  
  46.         delete this;  
  47.     }  
  48. }  
他在delete之前先判断了下这个obj是否还在管理池中。细想一下这里真的存在一个危险的问题,那就是如果你把一个obj添加到池中了。然后你池中保存了这个obj的指针,但是如果你在添加到池的这一帧中又release了1次这个obj,那么会导致这个内存池中存了一个野指针,那么接下来等着你的就是程序挂掉了。

4、总结:

Cocos2dx 中内存管理是基于 引用计数(Reference Count)和自动释放池(AutoReleasePool)和 内置CCVector和其它Cocos2dx内置容器类辅助实现的,而且要注意的是它是按帧管理的。也就是它只负责你在前一帧中那些没有被利用到的obj帮你自动处理了。cocos2dx 中 class的 static create 接口都将产生 autorelase 的obj,如果autorelease的obj在产生后没有找到管理的宿主(执行retain),那么就会在下一帧被PoolManager 给clear掉。

关于cocos2dx 中内存池中这个隐含的危险。我们只要注意不要在同一帧中将一个obj加入池中又将其release就行了。

另外cocos2dx 中存在大量的单例对象,官方可能存在忘记释放这些单例对象的情况,这时候我们可以通过 vld 快速的定位到,自己进行对应的修改是指能够被正确释放掉。

这篇关于cocos2dx 学习(-)内存管理机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1033101

相关文章

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen