CocosCreator内存调试技巧H5版

转自 http://www.cnblogs.com/billyrun/articles/7257742.html

 

本篇包含如下干货

1.JavaScript垃圾回收机制理解

2.CocosCreator内存泄漏排查与管理

3.Chrome内存调试技巧

 

JavaScript垃圾回收机制理解

js内存策略主要有标记清除和引用计数两种

具体由浏览器实现

简单来讲如果有全局变量

window.val = [...]//10000个数字的数组

显然这时val要占用一部分内存

若重新赋值

window.val = null

在仅有以上两条语句的情况下

之前的数组会被清除掉,所占用的内存会自动回收

回收不是立刻进行的,但一般来讲也很快

函数中的局部变量在函数结束之后,也会进入自动回收队列

所以通常js开发只需要关心全局变量

并不需要过多担心内存问题

当然,结合了游戏引擎之后就不一样了!以下会结合例子说明

 

CocosCreator内存泄漏排查与管理

在内存管理方面

CocosCreator开发H5游戏与原生游戏可以说完全不同

H5游戏使用js自身的内存管理策略,就是上文所说的

而原生平台使用jsb技术依靠cpp层面的引用记数来管理节点、纹理等内存,和2dx时代一样

区别有多明显呢?

比如H5版本中

window.node = new cc.Node()

那边全局变量node会一直可用,占用内存,直到手动destroy为止

而2dx-lua中

node = CCNode:create()

同样是全局变量,但由于create方法设置了autoRelease

若不增加引用记数(比如加入场景),那么node只能'存活'一帧的时间,下一帧就被释放掉了

 

在H5游戏开发中,绝大多数节点创建时作为局部变量

若未加入场景,则上下文结束后,标记清除等待回收

若加入了场景,实际上全局场景队列会保留其引用,因此不会被清除

若游戏内设置全局变量保存某节点,且其parent==null,此节点也会常驻内存,可以理解为用户有意保留不做销毁

 

内存问题举例

开发过程中遇到了一个造成内存严重泄漏的bug

按钮由工厂方法创建,若未加入场景,导致内存泄漏

与上文节点的创建不同之处在于,按钮注册了on('click',...)或on('touchend'...)回调函数

因此其引用被保存在cc.eventManager全局变量中

下面通过具体的调试来论证这一点

介绍排查内存泄漏问题的基本方法

ps.若单纯创建节点,不加入场景也不注册点击事件,那么该节点或精灵是可以被自动回收的

 

Chrome内存调试技巧

首先升级Chrome至最新版本(本文使用59.0.3071.115)

然后打开'开发者工具' 选择Memory页签 选择Take heap snapshot

可以看到出示内存19.2MB

接下来我们创建5000个按钮节点

不加入场景也不保存引用

可以看到,我们只为按钮注册了touchend事件

并未保存引用或加入场景,然而页面内存激增至32.1MB

原因就在于cc.eventManager全局变量保留了每一个按钮节点的引用

导致按钮节点不会自动回收

 

引擎源码查考

CCNode:on方法有这样一段

复制代码

this._touchListener = cc.EventListener.create({
    event: cc.EventListener.TOUCH_ONE_BY_ONE,
    swallowTouches: true,
    owner: this,
    mask: _searchMaskInParent(this),
    onTouchBegan: _touchStartHandler,
    onTouchMoved: _touchMoveHandler,
    onTouchEnded: _touchEndHandler
});
if (CC_JSB) {
    this._touchListener.retain();
}
cc.eventManager.addListener(this._touchListener, this);
newAdded = true;

复制代码

注意owner就是按钮节点

该引用保存在_touchListener中并被加入cc.eventManager

又经过CCEventManager:_forceAddEventListener加入cc.eventManager._listenersMap

复制代码

_forceAddEventListener: function (listener) {
    var listenerID = listener._getListenerID();
    var listeners = this._listenersMap[listenerID];
    if (!listeners) {
        listeners = new _EventListenerVector();
        this._listenersMap[listenerID] = listeners;
    }
    listeners.push(listener);

    if (listener._getFixedPriority() === 0) {
        this._setDirty(listenerID, this.DIRTY_SCENE_GRAPH_PRIORITY);

        var node = listener._getSceneGraphPriority();
        if (node === null)
            cc.logID(3507);

        this._associateNodeAndEventListener(node, listener);
        if (node.isRunning())
            this.resumeTarget(node);
    } else
        this._setDirty(listenerID, this.DIRTY_FIXED_PRIORITY);
},

复制代码

调用_associateNodeAndEventListener时又加入cc.eventManager._nodeListenersMap

复制代码

_associateNodeAndEventListener: function (node, listener) {
    var listeners = this._nodeListenersMap[node.__instanceId];
    if (!listeners) {
        listeners = [];
        this._nodeListenersMap[node.__instanceId] = listeners;
    }
    listeners.push(listener);
},

复制代码

 

内存分析图解

了解了代码来龙去脉之后

我们从内存分析的视角来找问题

从上图所示占有内存最多的Object着手

可以看到其中一个疑似泄漏内存对象的引用如下图

正是通过_forceAddEventListener加入的_nodeListenersMap和_listenersMap

在分别打开可以看到引用具体所在信息

_nodeListenersMap和_listenersMap都可以找到owner

即5000个之中的按钮节点

再详细查其实可以确定其instanceID与我们生成时是一致的

与查看源码时获得的信息一致

关掉Object打开cc_Node

找到疑似问题节点

同样可以看到其引用关系

 

验证的最后一步

我们在console中输入以下语句

清除我们刚刚发现的引用

cc.eventManager._listenersMap.__cc_touch_one_by_one._sceneGraphListeners = {}
cc.eventManager._nodeListenersMap = {}

再次计算内存,发现内存回到初始值,5000个按钮节点被释放回收!

 

总结

遇到具体内存泄漏问题时

往往是从开发者工具Memory反应的信息着手倒推

找到问题代码出现的源头

这次内存调试,发现了我们程序代码中的写法错误

杜绝类似'创建按钮后不使用'这样的行为之后

游戏的内存状况得到了大大改善

 

此外还有一点dragonBones使用的小经验

dragonBones.CCFactory.getFactory().clear()

db会cache许多动画数据信息甚至可以多至近百兆

及时清理也可以解决内存不足问题

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值