JSB内存管理

简介: <p style="margin-top:0px; margin-bottom:22px; line-height:24px; font-size:15px; color:rgb(51,51,51); font-family:'Microsoft Yahei','冬青黑体简体中文 w3',宋体"> 原文地址:<a target="_blank" href="http://www.coco

原文地址:http://www.cocos2d-x.org/wiki/Memory_Management_of_JSB by u0u0
翻译:晋文格墨

JSB的内存管理
基于Cocos2d-x 2.15,但同样适用于Cocos2d-x 3.0。

JSB对象的生命周期
总所周知,javascript有自己的内存管理机制,即垃圾回收。Cocos2d-x模拟垃圾回收系统来管理Cocos对象。但这里有一个问题,就是将Cocos2d-x对象绑定到javascript对象时由谁负责内存管理。

先看一个案例。
通过XXX.create()来分配对象内存

下面的代码分配了一个全局变量。

gnode = cc.Node.create();

gnode并没有通过addChild()添加到其它cc.Node中。
在菜单项的回调函数中添加如下代码:

// menuItem callback
onButton:function (sender) {
    sender.addChild(gnode);
}

当点击此按钮,你将会看到如下错误信息:

Cocos2d: jsb: ERROR: File /Users/u0u0/Documents/project/SK_parkour/scripting/javascript/bindings/generated/jsb_cocos2dx_auto.cpp: Line: 3010, Function: js_cocos2dx_CCNode_addChild
Cocos2d: Invalid Native Object

发生了什么!“Invalid Native Object”是什么意思?
在javascript中gnode是一个全局变量,意味着它不能被回收。
但是gnode里的CCNode会被Cocos2d-x回收。
为了弄清楚这个问题,你需要了解一下spidermonkey并深入研究javascript的绑定代码。
cc.Node.create()的内部实现
详细的实现代码如下:

static JSFunctionSpec st_funcs[] = {
    JS_FN("create", js_cocos2dx_CCNode_create, 0, JSPROP_PERMANENT | JSPROP_ENUMERATE),
    JS_FS_END
};

jsb_CCNode_prototype = JS_InitClass(
    cx, global,
    NULL, // parent proto
    jsb_CCNode_class,
    js_cocos2dx_CCNode_constructor, 0, // constructor
    properties,
    funcs,
    NULL, // no static properties
    st_funcs);

cc.Node.create()被对应的C函数是js_cocos2dx_CCNode_create(),如下:

JSBool js_cocos2dx_CCNode_create(JSContext *cx, uint32_t argc, jsval *vp)
{
    if (argc == 0) {
        cocos2d::CCNode* ret = cocos2d::CCNode::create();
        jsval jsret;
        do {
        if (ret) {
            js_proxy_t *proxy = js_get_or_create_proxy(cx, ret);
            jsret = OBJECT_TO_JSVAL(proxy-&gt;obj);
        } else {
            jsret = JSVAL_NULL;
        }
    } while (0);
        JS_SET_RVAL(cx, vp, jsret);
        return JS_TRUE;
    }
    JS_ReportError(cx, "wrong number of arguments");
    return JS_FALSE;
}

通过cocos2d:CCNode::create()成功分配的对象将会被封装成js_get_or_create_proxy()创建的一个新对象js_proxy_t。
在js_get_or_create_proxy()函数里,只要关注下面这行代码:

JS_AddObjectRoot(cx, &amp;proxy-&gt;obj);

这段代码将一个JSObject添加到垃圾回收器的根集合中的spidermonkey api。proxy->obj是对应javascript里的一个JSObject。
所以通过cc.Node.create()分配的对象将会一直保留在内存中,直到调用JS_RemoveObjectRoot()。
但是cocos2d::CCNode::create()是一个自动释放对象,它会在下一个游戏帧被Cocos2d-x回收。
CCobject的析构函数将会被调用,请注意下面的代码:

// if the object is referenced by Lua engine, remove it
if (m_nLuaID)
{
    CCScriptEngineManager::sharedManager()-&gt;getScriptEngine()-&gt;removeScriptObjectByCCObject(this);
}
else
{
    CCScriptEngineProtocol* pEngine = CCScriptEngineManager::sharedManager()-&gt;getScriptEngine();
    if (pEngine != NULL &amp;&amp; pEngine-&gt;getScriptType() == kScriptTypeJavascript)
    {
        pEngine-&gt;removeScriptObjectByCCObject(this);
    }
}

pEngine->removeScriptObjectByCCObject 做了一件神奇的事情。

void ScriptingCore::removeScriptObjectByCCObject(CCObject* pObj)
{
    js_proxy_t* nproxy;
    js_proxy_t* jsproxy;
    void *ptr = (void*)pObj;
    nproxy = jsb_get_native_proxy(ptr);
    if (nproxy) {
        JSContext *cx = ScriptingCore::getInstance()-&gt;getGlobalContext();
        jsproxy = jsb_get_js_proxy(nproxy-&gt;obj);
        JS_RemoveObjectRoot(cx, &amp;jsproxy-&gt;obj);
        jsb_remove_proxy(nproxy, jsproxy);
    }
}

JS_RemoveObjectRoot函数将JSobject从javascript根集合中移除。 jsb_remove_proxy将proxy(委托)从hash表中移除。
现在我们可以解释本文开始提出的问题了。

Cocos2d-x的垃圾回收系统负责内存管理

回到gnode,它是一个全局变量。CCObject的析构函数JS_RemoveObjectRoot的作用只是平衡JS_AddObjectRoot的创建。Spidermonkey将不会回收这个全局变量,但是gnode的本地对象将会被释放。访问gnode的本地对象将会产生之前看到的那个错误。
通过new分配对象
思考一下下面的代码:

gnode=new cc.Node;

为了找到正确答案,同样需要深入研究JSB代码。
如之前提到的,cc.Node的构造函数是js_cocos2dx_CCNode_constructor()。
请注意下面的代码:

if (argc == 0) {
    cocos2d::CCNode* cobj = new cocos2d::CCNode();
    cocos2d::CCObject *_ccobj = dynamic_cast(cobj);
    if (_ccobj) {
        _ccobj-&gt;autorelease();
    }

本地对象被压入到Cocos2d-x的自动释放池中。所以new和create()是一样的。

关于retain()和release()

有两个函数可以用于手动控制对象的生命周期。如果你想避免之前例子中产生的错误。
你可以有以下两个选择:

1.将gnode添加到其它的CCNode中,addChild()将gnode保留在内部。
2.在create()之后立即调用gnode.retain()。

在第二种情况下,你需要在合适的时候调用gnode.release()以防止内存泄漏。下一节将会介绍它。

ctor()和onExit()

Cocos2d-x JSB使用Simple JavaScript Inheritance By John Resig。但是构造函数的名字不一样。
在JSB中,ctor是构造函数。对应的onExit则扮演的是析构函数,它会在CCNode释放之前被调用。
下面的例子演示了如何手动控制JSB对象的生命周期。

var container = cc.Node.extend({
    ctor:function () {
        this._super();
        this.gnode = cc.Node.create();
        this.gnode.retain();
    },
    onExit:function() {
        this.gnode.release();
        this._super();
    },
});
相关文章
|
7月前
|
存储 C语言 C++
C/C++内存管理--1
C/C++内存管理--1
|
7月前
|
存储 编译器 C语言
C/C++内存管理--2
C/C++内存管理--2
|
7月前
|
JavaScript 前端开发 Java
CocosCreator 面试题(十)Cocos Creator 内存管理
CocosCreator 面试题(十)Cocos Creator 内存管理
417 0
|
7月前
|
存储 C语言 C++
lesson4-C++内存管理
lesson4-C++内存管理
72 0
|
SQL 缓存 Java
PHP内存泄漏看这一篇就够了!
所谓的内存泄漏就是忘记释放内存,导致进程占用的物理内存(附1)持续增长,得益于 PHP 的短生命周期,PHP 内核有一个关键函数叫做php_request_shutdown此函数会在请求结束后,把请求期间申请的所有内存都释放掉,这从根本上杜绝了内存泄漏,极大的提高了 PHPer 的开发效率,同时也会导致性能的下降。
1474 0
|
JavaScript API C++
js内存管理系列篇二:内存泄漏,内存管理
上篇文章讲到 js 内存泄漏存在的各种表现以及问题。现在来说说常见代码中的内存泄漏。
110 0
js内存管理系列篇二:内存泄漏,内存管理
|
存储 JavaScript 前端开发
js内存管理系列篇一:内存分析
我是web光明顶一期的学生,本文是对深究JS原理这个阶段的分析。
144 0
js内存管理系列篇一:内存分析
|
算法 JavaScript 前端开发
js内存管理系列篇三:垃圾回收机制
js作为一门高级程序语言,具有自动的垃圾回收机制,那我们来探究一下它的原理及机制。
151 0
|
SQL 物联网 Linux
跨平台开发--C# 使用C/C++生成的动态链接库
跨平台开发--C# 使用C/C++生成的动态链接库
413 0
跨平台开发--C# 使用C/C++生成的动态链接库
|
Java C# C++
CPython内存管理机制
CPython(Python解释器)是如何管理对象的生命周期 目前的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。
1089 0