Spidermonkey和垃圾回收

3
我将在我的C++应用程序中嵌入Spidermonkey。我需要在本地C++中实现一些自定义JavaScript函数,这些函数传递一个jsval。我需要保护jsval以防止意外的垃圾回收。以下操作是否正确:
(1)在初始化程序中:
static jsval vp; // a STATIC variable, value unknown
JSBool init((JSContext *cx, uintN argc, jsval *vp) {
   JS_AddValueRoot(cx,  &vp);
}

(2)在一个实现JavaScript函数setter()的C++函数中:
JSBool setter(JSContext *cx, uintN argc, jsval *vp) {
   ...
  vp=...;// set to some JSObject and hopefully makes any previous JSObject available for gc

}

(3) 在同一编译单元中实现Javascript函数getter()的第二个C++函数调用:

JSBool getter(JSContext *cx, uintN argc, jsval *vp) {
  jsval somethingelse = vp; //directly retrieve the static value stored by setter()
  ....
}

我的Javascript脚本使用这样的函数调用方式:
init();
setter(...);
some_other_function_call_that_causes_gc();
getter();
setter(...);
some_other_function_call_that_causes_gc();
getter();
....
some_other_function_call_that_causes_gc();
setter(...);
some_other_function_call_that_causes_gc();
getter();

请注意,我从不调用JS_RemoveRoot(),因为静态jsval vp是在两个函数调用之间传递的my jsval的永久存储。而且,我在我的setter()中不断将新值设置到gc根静态变量vp中,认为任何以前存储在jsval中的JSObject都可以进行垃圾回收。

这些是否是创建可在函数调用之间传递的gc根临时变量的正确方式?特别地,我的setter()替换以前的JSObject的方式是使现有的JSObject可用于gc的正确方式吗(即没有内存泄漏/崩溃)。

编辑:我相信垃圾回收是一个问题的原因是:

https://developer.mozilla.org/En/SpiderMonkey/JSAPI_User_Guide

在JSAPI概念、Javascript值一节下:

jsval本身不能保护其引用对象免受垃圾回收器的影响

https://developer.mozilla.org/en/SpiderMonkey_Garbage_Collection_Tips

例3说“Root as you go”,并展示了一个jsval必须被分配为根的示例。


我不明白为什么这个变量会有垃圾回收的风险?您能指出来吗? - Jivings
因为在我的Javascript脚本中,在setter()和getter()之间调用了其他的Javascript函数。请注意,setter()设置其值,稍后另一个函数getter()获取该值。 - JavaMan
不管有多少函数,只要没有引用它,某个东西就会被当作垃圾回收(GC)。也许我没有理解问题。你有证据表明 GC 正在删除你需要的变量吗? - Jivings
我不确定。我对spidermonkey完全是新手。但JSAPI用户指南说,jsval无法保护其引用对象免受垃圾回收的影响。因此,即使我的jsval引用了一个JSObject,该JSObject也可能被垃圾回收,导致我的jsval成为悬空指针。请参见我的编辑。 - JavaMan
2个回答

0

对于像jsval这样的东西,没有必要添加根。您只需要在脚本中保持对该值的引用即可。SpiderMonkey GC基于引用计数,因此只要在当前范围内引用它们,您的jsvals就不会消失:

var x = 5;

callNativeFun(x);

function foo() { throw true; }

print(nativeFunRef());

//脚本到此结束。X和FOO将被垃圾回收,以及callNativeFun和nativeFunRef js函数签名。

在上面的代码示例中,x和foo都由全局对象保持引用。

只要脚本中定义了jsval指针内容,它就永远不会到达GC。确保您的二进制代码在其生命周期结束后从不使用该值;也就是当您在脚本中完成它并调用delete或通过值或作用域终止代理将其值设置为undefined时。如果您预见到任何这些负面交互,您应该在该jsval上设置GC根。


你可能想将值直接复制到二进制环境中,而不是将一些关键数据留在脚本的掌控之下,因为你无法预测其行为。通常情况下,如果你需要GC root某些东西,除非你正在使用JS_API内部,否则你的方法是错误的。 - user735796

0

涉及到SpiderMonkey 17及以上版本

jsval或任何其他GC对象在使用时应该被保护,如果有可能在GC对象使用期间触发GC。即使jsval引用已经受到保护的存储,这也是必需的。SpiderMonkey的GC是移动GC

使用RootedValue来保护基于堆栈的jsvalHeap<JS::Value>来保护基于堆的jsvaljsvalJS::Value相同)。对于函数的参数,请使用Handle<JS::Value>MutableHandle<JS::Value>

以下是RootingAPI注释的摘录:

 * A moving GC may change the physical location of GC allocated things, even
 * when they are rooted, updating all pointers to the thing to refer to its new
 * location. The GC must therefore know about all live pointers to a thing,
 * not just one of them, in order to behave correctly.
 *
 * For a code fragment such as:
 *
 * JSObject *obj = NewObject(cx);
 * DoSomething(cx);
 * ... = obj->lastProperty();
 *
 * If |DoSomething()| can trigger a GC, the stack location of |obj| must be
 * rooted to ensure that the GC does not move the JSObject referred to by
 * |obj| without updating |obj|'s location itself. This rooting must happen
 * regardless of whether there are other roots which ensure that the object
 * itself will not be collected.
 *
 * If |DoSomething()| cannot trigger a GC, and the same holds for all other
 * calls made between |obj|'s definitions and its last uses, then no rooting
 * is required.
 *
 * SpiderMonkey can trigger a GC at almost any time and in ways that are not
 * always clear. For example, the following innocuous-looking actions can
 * cause a GC: allocation of any new GC thing; JSObject::hasProperty;
 * JS_ReportError and friends; and ToNumber, among many others. The following
 * dangerous-looking actions cannot trigger a GC: js_malloc, cx->malloc_,
 * rt->malloc_, and friends and JS_ReportOutOfMemory.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接