CocosCreator內存調試技巧H5版


本篇包含如下干貨

1.JavaScript垃圾回收機制理解

2.CocosCreator內存泄漏排查與管理

3.Chrome內存調試技巧

轉載請注明原文地址

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

 

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許多動畫數據信息甚至可以多至近百兆

及時清理也可以解決內存不足問題

 

參考文獻

http://www.cnblogs.com/mizzle/archive/2011/08/12/2135838.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM