如何演示 V8 弱指针回调?

How to demonstrate V8 Weak Pointer callback?

我正在考虑将 V8 用作项目的嵌入式 JavaScript 引擎,但我无法弄清楚如何管理本机 C++ 对象的生命周期。本实验旨在演示弱指针回调。

接近下面代码末尾时,我调用 v8::Persistent::SetWeak 并安装回调。我想要的只是能够创建调用此回调的演示。

我半心半意地希望这就像让句柄超出范围一样简单,但下面的代码没有调用回调。我还在某处读到调用 Isolate::IdleNotificationDeadline 可能会强制进行垃圾回收,但这也不起作用。

如何演示弱指针回调被调用?我想编写一些代码,使 cleanup 函数在程序退出前的某个时刻被调用。

我显然无法理解如何正确设置它,希望得到一些帮助和解释。恐怕我还不明白。

我的期望是,可以通过 Persistent 句柄创建弱指针,并且当没有更多对象句柄时,将(最终)调用回调,以便关联本机 C++ 资源JavaScript 对象可以被释放。

v8.h 头文件中的注释让我特别反感:

NOTE: There is no guarantee as to when or even if the callback is invoked. The invocation is performed solely on a best effort basis. As always, GC-based finalization should not be relied upon for any critical form of resource management!

这让我觉得整个引擎对于使用这种机制管理本机对象毫无用处。但我相信至少有一些最小的人为场景,其中回调 调用。

我的要求是我能够编写一些 JavaScript 来分配一个对象,当没有更多的引用时,该对象最终将被释放。

foo = createFoo(); // creates a JavaScript object wrapping the native C++ Foo object.
doSomethingWith(foo); // do stuff with the Foo here
foo = null; // make sure there are no more JavaScript handles to the wrapper for the Foo object.
// After this point, I'm hoping V8 will eventually let me know that I can delete the native C++ Foo object

我假设我实际上不必执行任何 JavaScript 来演示弱指针和清理机制。我希望我可以创建一个 Persistent 句柄并安装 Weak 回调,然后让它超出范围。我的那个假设似乎是错误的,或者我没有在这里证明它。

#include <iostream>
#include "include/libplatform/libplatform.h"
#include "include/v8.h"

class Foo {};

void cleanup(const v8::WeakCallbackInfo<Foo>& data)
{
  std::cout << "Weak Callback called" << std::endl;
  delete data.GetParameter();
}

int main(int argc, char* argv[]) {
  std::cout << "Start..." << std::endl;

  v8::V8::InitializeICUDefaultLocation(argv[0]);
  v8::V8::InitializeExternalStartupData(argv[0]);
  std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(platform.get());
  v8::V8::Initialize();

  // Create a new isolate and make it the current one
  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
  v8::Isolate* isolate = v8::Isolate::New(create_params);

  {
    v8::Isolate::Scope isolate_scope(isolate);
    v8::HandleScope handle_scope(isolate);

    v8::Local<v8::Context> context = v8::Context::New(isolate, NULL, v8::ObjectTemplate::New(isolate));
    v8::Context::Scope context_scope(context);

    v8::Local<v8::Object> obj = v8::ObjectTemplate::New(isolate)->NewInstance(context).ToLocalChecked();
    v8::Persistent<v8::Object> persistent;
    persistent.Reset(isolate, obj);
    persistent.SetWeak(new Foo(), cleanup, v8::WeakCallbackType::kParameter);
  }

  isolate->IdleNotificationDeadline(1.0);

  std::cout << "...Finish" << std::endl;
}

注意:上面的代码示例应该以与 hello_world example for V8 is built.

相同的方式构建

举一个人为的例子,调用 isolate->LowMemoryNotification() 应该可以解决问题。不过,我不建议在生产中这样做,因为这是对 CPU 时间的巨大浪费(除非你 真的 内存不足,因为到 OOM)。

除此之外,您找到的评论有效。依靠弱回调来释放对象是可以的;不建议依赖它来管理关键和稀缺资源。如果有问题的对象加起来很大,您应该适当地使用 isolate->AdjustAmountOfExternalAllocatedMemory(...),让 GC 知道有东西要释放。当 Isolate 消失时,你应该有自己的后备机制来清理所有内容(如果你不终止整个过程的话)。