COCOS学习笔记--Cocod2dx内存管理(三)-Coco2d-x内存执行原理
通过上两篇博客。我们对Cocos引用计数和Ref类、PoolManager类以及AutoreleasePool类已有所了解,那么接下来就通过举栗子来进一步看看Coco2d-x内存执行原理是如何的。
//先建一个node
Node * node = Node::create();
//创建完之后打印node的引用计数
schedule([node](float f){
//获得node的引用计数
int count = node->getReferenceCount();
//打印node的引用计数
log("node's ReferenceCount = %d",count);
},"test");
打印结果例如以下:
能够看到引用计数打印出来是一大串的整数,事实上这时node已经被释放掉了是不存在的。引用计数为0,所以系统随便打印了一堆整数来表示。
我们先看一下Node类的源代码,其create()方法例如以下:
Node * Node::create()
{
Node * ret = new (std::nothrow) Node();
if (ret && ret->init())
{
ret->autorelease();
}
else
{
CC_SAFE_DELETE(ret);
}
return ret;
}
看到这句Node * ret = new (std::nothrow) Node();首先new了一个node,我们知道Node是继承自Ref的。通过new创建对象就会调用其构造函数,而之前我们在Ref类的解说中知道:调用Ref构造函数中会运行这句_referenceCount(1)对其引用计数+1,所以node对象起始的引用计数为1。
我们还能够看到这句:ret->autorelease(),仅仅要通过create创建的node都会被加入到自己主动释放池中,我们在看下autorelease()源代码:
Ref* Ref::autorelease()
{
PoolManager::getInstance()->getCurrentPool()->addObject(this);
return this;
}
既然node对象起始的引用计数为1。为什么在打印时它的引用计数就为0了呢?node对象是在什么时候被释放的呢?
先别急。我们先让node不被释放,怎样使node不被释放,有两种方法:
方法1:通过retain()方法添加node引用计数
//添加node的引用计数
node->retain();
node最開始创建后引用计数为1,调用retain()方法使其引用计数再+1,此时node的引用计数为2。但刚刚也看到了,引用计数開始后不知什么时候减了1,所以打印出来应该是1。执行一下:
能够看出,通过调用node的retain()方法能够使其引用计数+1。
方法2:通过addChild()方法将node加入到父节点上
this->addChild(node);
执行效果例如以下:
能够看到。通过addChild()方法也能够使node的引用计数+1,这是为什么呢?
我们再看一下Node类的源代码:
void Node::addChild(Node *child)
{
CCASSERT( child != nullptr, "Argument must be non-nil");
this->addChild(child, child->_localZOrder, child->_name);
}
能够看到node的addChild()方法调用了其重载的addChild()方法,那我们就接着看这重载的addChild()方法都做了什么:
void Node::addChild(Node *child, int localZOrder, int tag)
{
CCASSERT( child != nullptr, "Argument must be non-nil");
CCASSERT( child->_parent == nullptr, "child already added. It can't be added again"); addChildHelper(child, localZOrder, tag, "", true);
}
在重载addChild()方法中我们看到调用了这句:
addChildHelper(child, localZOrder, tag, "", true);
这句是干什么的呢我们接着看:
void Node::addChildHelper(Node* child, int localZOrder, int tag, const std::string &name, bool setTag)
{
...... this->insertChild(child, localZOrder); ......
}
在Node::addChildHelper()方法中调用了这句:
this->insertChild(child, localZOrder)
那我们就继续看insertChild()方法:
void Node::insertChild(Node* child, int z)
{
_transformUpdated = true;
_reorderChildDirty = true;
_children.pushBack(child);
child->_localZOrder = z;
}
我们看到了运行了这句代码:
_children.pushBack(child);
这句非常关键。我们到CCvector.h文件里看它的详细实现:
void pushBack(T object)
{
CCASSERT(object != nullptr, "The object should not be nullptr");
_data.push_back( object );
object->retain();
}
能够看到。通过pushBack方法将传来的child对象加入到了_data这个数据结构中,然后对child运行了其retain()方法,这下大家都明确了吧!为什么调用addChild()方法会使child的引用计数+1,由于它最后还是调用了retain()方法。
好了。以上两种添加引用计数的方法介绍完了。回过头来,刚刚另一个问题一直没有解决:就是我们创建的node明明在创建后引用计数为1,假设不人为通过以上2种方法添加其引用计数,为什么程序一启动引用计数就变成0了呢?这个node是在什么时候被释放的呢?
接下来我就为大家解答一下:
之前在我写渲染流程的博客(http://blog.csdn.net/gzy252050968/article/details/50414407)中提到过,引擎的入口函数是CCApplication类的run()方法。
int Application::run()
{
......
director->mainLoop();//进入引擎的主循环
......
return 0;
}
在run()方法中进入了游戏的主循环mainLoop()。我们设置的帧率就是mainLoop()方法每秒运行的次数。一般默认是每秒运行60次。我们游戏的渲染、内存管理等等全都是在这个mainLoop()方法里不断运行的。
我们再看一下这个主循环mainLoop():
void DisplayLinkDirector::mainLoop()
{
if (_purgeDirectorInNextLoop)//进入下一个主循环,也就是结束这次的主循环。就净化,也就是一些后期处理
{
_purgeDirectorInNextLoop = false;
purgeDirector();
}
else if (_restartDirectorInNextLoop)
{
_restartDirectorInNextLoop = false;
restartDirector();
}
else if (! _invalid)
{
drawScene();//绘制屏幕
PoolManager::getInstance()->getCurrentPool()->clear();//释放一些没实用的对象。主要保件内存的合理管理
}
}
我们能够看到这句代码:
PoolManager::getInstance()->getCurrentPool()->clear();
这句就是在每一帧结束时释放没实用到的对象,详细过程是先通过PoolManager::getInstance()方法获得PoolManager的单例对象,然后再通过getCurrentPool()方法得到当前的自己主动释放池对象,最后运行AutoreleasePool的clear()方法。clear()方法我在上一篇博客里写过,这里再去CCAutoreleasePool.cpp中看一下:
void AutoreleasePool::clear()
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = true;//设置为运行了清空操作
#endif
std::vector<Ref*> releasings;
releasings.swap(_managedObjectArray);
//遍历自己主动释放池managedObjectArray里存放的全部的Ref
for (const auto &obj : releasings)
{
//调用obj的release(),对obj的引用计数-1(假设对象引用计数为0则删除)
obj->release();
}
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
_isClearing = false;//设置为未运行清空操作
#endif
}
clear()方法就是AutoreleasePool对象把自己维护的队列managedObjectArray里面每个obj都运行release()。
release()方法我的上上篇博客里也介绍过,这里再看一下加深记忆:
void Ref::release()
{
CCASSERT(_referenceCount > 0, "reference count should be greater than 0");
//对其引用计数值进行-1
--_referenceCount;
//引用计数为0,删除对象
if (_referenceCount == 0)
{
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
auto poolManager = PoolManager::getInstance();
if (!poolManager->getCurrentPool()->isClearing() && poolManager->isObjectInPools(this))
{
CCASSERT(false, "The reference shouldn't be 0 because it is still in autorelease pool.");
}
#endif
//从保存Ref*的list中删除
#if CC_REF_LEAK_DETECTION
untrackRef(this);
#endif
//删除该对象
delete this;
}
}
看到这句了没?
--_referenceCount;
看到这句了没?
delete this;
release()方法分两部分,先对引用计数-1,然后推断引用计数是否为0。若为0则删除对象。
假设我们仅仅通过create()去创建一个对象,而不去调用其retain()方法,那么这个对象就会在下一帧被释放掉。
这回你理解为什么我们一開始创建的node直接就被释放掉了吧。
好了,重点来了。我们看到人为通过以上2种方法让引用计数+1后,打印出来的引用计数一直是1,按理来说,它们是通过create()创建出来的。在每一帧结束后AutoreleasePool::clear()方法中也会调用其release()方法。这一帧是1。下一帧就该减1变成 0了啊,为什么没有变成0被释放掉呢?
为什么啊?为什么啊?
事实上是这种,我们每次运行AutoreleasePool::clear()后,都会对AutoreleasePool维护的队列_managedObjectArray运行一次clear(),也就是说在下一帧的时候。自己主动释放池里已经不存在这些node了,所以在AutoreleasePool::clear()中便不会运行之前这些node的release()方法。这些node在下一帧并不会被释放,这就是为什么node的引用计数打印出来一直是1,说白了就是在AutoreleasePool::clear()中每一个node仅仅会运行一次release()方法。
那么接下来你可能会想,既然在自己主动释放池中仅仅运行一次node的release()方法,那么怎样去删除node呢?
非常easy:之前介绍了2种添加node引用计数的方法,1是retain()方法2是addChild()方法,那么要删除node的方法与以上2个一一相应:
方法一.通过release()方法降低node引用计数;
方法二.通过removeFromParent()方法将node从父节点上移除。
removeFromParent()方法事实上也是在其方法中运行release()方法进行了删除操作。但该方法不是仅仅运行了单纯的删除操作,它还从渲染树中将node移除。
Cocos2d-x是通过渲染树进行渲染的。对cocos引擎渲染流程不是非常了解的能够看看我之前的博客,cocos是将全部节点加入到渲染树上进行渲染的。
最后总结一下:
1.create出的node对象起始引用计数为1;
2.添加node的引用计数方法有2种:调用retain()方法使引用计数直接+1或通过addChild()方法将node加入到父节点上使其引用计数+1;
3.降低node的引用计数方法有2种:调用release()方法使引用计数直接-1或通过removeFromParent()方法将node从父节点上移除。
4.主循环mainLoop()方法中在每帧都会运行AutoreleasePool::->clear()释放自己主动释放池中没实用到的对象;
5.假设仅仅通过create()去创建一个对象node。而不去调用其retain()方法。那么这个node就会在下一帧被释放掉。
好了,关于Cocos引擎内存管理的全部内容就都OK了。花了一个周末连学习带总结,累死宝宝了<@_@>
COCOS学习笔记--Cocod2dx内存管理(三)-Coco2d-x内存执行原理的更多相关文章
- Java虚拟机内存溢出异常--《深入理解Java虚拟机》学习笔记及个人理解(三)
Java虚拟机内存溢出异常--<深入理解Java虚拟机>学习笔记及个人理解(三) 书上P39 1. 堆内存溢出 不断地创建对象, 而且保证创建的这些对象不会被回收即可(让GC Root可达 ...
- Linux学习笔记(六) 进程管理
1.进程基础 当输入一个命令时,shell 会同时启动一个进程,这种任务与进程分离的方式是 Linux 系统上重要的概念 每个执行的任务都称为进程,在每个进程启动时,系统都会给它指定一个唯一的 ID, ...
- 操作系统学习笔记4 | CPU管理 && 多进程图像
操作系统的核心功能就是管理计算机硬件,而CPU就是计算机中最核心的硬件.而通过学习笔记3的简史回顾,操作系统通过多进程图像实现对CPU的管理.所以多进程图像是操作系统的核心图像. 参考资料: 课程:哈 ...
- java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)
概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...
- Linux学习笔记(五) 账号管理
1.用户与组账号 用户账号:包括实际人员和逻辑性对象(例如应用程序执行特定工作的账号) 每一个用户账号包含一个唯一的用户 ID 和组 ID 标准用户是系统安装过程中自动创建的用户账号,其中除 root ...
- Qt学习笔记-Widget布局管理
Qt学习笔记4-Widget布局管理 以<C++ GUI Programming with Qt 4, Second Edition>为参考 实例:查找对话框 包含三个文件,f ...
- Linux内核学习笔记-2.进程管理
原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- 1.C#基础学习笔记3---C#字符串(转义符和内存存储无关)
技术qq交流群:JavaDream:251572072 教程下载,在线交流:创梦IT社区:www.credream.com ------------------------------------- ...
- Go语言内存管理(一)内存分配
Go语言内存管理(一)内存分配 golang作为一种"高级语言",也提供了自己的内存管理机制.这样一方面可以简化编码的流程,降低因内存使用导致出现问题的频率(C语言使用者尤其是初学 ...
随机推荐
- (Go)02.go 安装delve调试工具测试
安装调试工具 go get github.com/derekparker/delve/cmd/dlv 增加断点调试 调试--->启动调试
- Aspose.Words将Word模板打印出来
利用Aspose.Words将制作好的模板,输出出来自己所需的文件 /// <summary> /// 打印信息和列表 /// </summary> /// <param ...
- (转)RabbitMQ学习之spring整合发送异步消息(注解实现)
http://blog.csdn.net/zhu_tianwei/article/details/40919249 实现使用Exchange类型为DirectExchange. routingkey的 ...
- Surrogate data 代理数据
附一篇science论文,待啃: 附Surrogate time series and fields,matlab:https://www.sogou.com/link?url=DSOYnZeCC_p ...
- better-scroll的使用方法,动态创建dom使用better-scroll
移动端经常会用页面高度超过了手机屏幕的高度,但是有没有滚动条的出现这时候就用 better-scroll 这个插件, iscroll 是常用的但是这个组件没有人在维护了,导致很多的问题没有办法解决. ...
- day01_20181223
今日内容大纲:1,python的出生于应用.2,Python的历史. python2x: 源码冗余,源码重复,源码不规范. python3x:源码清晰优美简单. ...
- python编写简单的html登陆页面(1)
1 html 打开调式效果如下 2 用python后台编写 # coding:utf-8# 从同一个位置导入多个工具,# 这些工具之间可以用逗号隔开,同时导入# render_template渲染 ...
- Windows Server菜鸟宝典之一:Windows Server 2008 R2 AD服务器搭建
1.对于将要安装成为DC的服务器来讲,其系统配置以及基本的磁盘规划在此就不在累述了,但是关键的网络连接属性是必须要注意的.可以通过打开本地连接的属性来进行配置其IP属性.作为服务器DC的IP地 ...
- vim牛逼的code工具: ctags+ cscope
自己总结 在我的工作目录里的.vimrc中做了这样的配置: set tags=tags; set autochdir 在项目根目录里利用"sudo ctags -R *", ...
- Css进阶练习(实现抽屉网样式布局)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...