【译】V8的Handle与垃圾回收
原文链接:https://v8.dev/docs/embed
Handle提供对JavaScript对象在堆中位置的引用。 V8垃圾收集器回收了无法再访问的对象所使用的内存。 在垃圾收集过程中,垃圾收集器通常将对象移动到堆中的不同位置。 当垃圾收集器移动对象时,垃圾收集器还会使用对象的新位置来更新所有引用该对象的Handle。
如果无法通过JavaScript访问该对象并且没有引用该对象的句柄,则该对象被视为垃圾。 垃圾收集器会不时删除所有被视为垃圾的对象。 V8的垃圾收集机制是V8性能的关键。
有几种类型的句柄:
- 本地句柄保存在堆栈中,并在调用适当的析构函数时删除。 这些句柄的生存期由句柄范围决定,该范围通常是在函数调用开始时创建的。 删除句柄作用域后,垃圾回收器可以自由地释放以前由句柄作用域中的句柄引用的那些对象,前提是它们不再可从JavaScript或其他句柄访问。 上面的hello world示例中使用了这种类型的句柄。
本地句柄具有Local <SomeType>类。
注意:句柄堆栈不是C ++调用堆栈的一部分,但是句柄作用域已嵌入C ++堆栈中。 处理范围只能是堆栈分配的,不能与new一起分配.
- 持久句柄就像本地句柄一样,提供了对堆分配的JavaScript对象的引用。 有两种类型,它们处理的引用的生存期管理不同。 当需要为多个函数调用保留对一个对象的引用时,或者当句柄寿命不对应于C ++范围时,请使用持久句柄。 例如,谷歌浏览器使用持久性句柄来引用文档对象模型(DOM)节点。 当对对象的唯一引用来自弱持久句柄时,可以使用PersistentBase :: SetWeak将持久句柄设为弱,以从垃圾回收器触发回调。
- UniquePersistent <SomeType>句柄依靠C ++构造函数和析构函数来管理基础对象的生存期。
- Persistent <SomeType>可以使用其构造函数进行构造,但必须使用Persistent :: Reset明确清除。
- 还有一些其他类型的句柄很少使用,这里我们仅简要提及:
- Eternal是用于永远不会被删除的JavaScript对象的持久句柄。 使用起来更便宜,因为它使垃圾收集器不必确定该对象的活动性。
- Persistent和UniquePersistent都不能复制,这使它们不适合用作C ++ 11之前的标准库容器中的值。 PersistentValueMap和PersistentValueVector提供持久性值的容器类,具有map和类似于vector的语义。 C ++ 11嵌入器不需要这些,因为C ++ 11的move语义解决了根本的问题。
当然,每次创建对象时创建本地句柄都会导致很多句柄! 这是句柄作用域非常有用的地方。 您可以将句柄作用域视为包含大量句柄的容器。 调用句柄作用域的析构函数时,将从堆栈中删除在该范围内创建的所有句柄。 如您所料,这导致句柄指向的对象可以被垃圾收集器从堆中删除。
回到我们非常简单的hello world示例,在下图中,您可以看到句柄堆栈和堆分配的对象。 请注意,Context :: New()返回一个Local句柄,并基于它创建一个新的Persistent句柄,以演示Persistent句柄的用法。
调用析构函数HandleScope ::~HandleScope时,将删除句柄范围。 如果没有其他引用,则在删除的句柄范围内由句柄引用的对象可以在下一个垃圾回收中删除。 垃圾收集器还可以从堆中删除source_obj和script_obj对象,因为它们不再被任何句柄引用或无法通过JavaScript访问。 由于上下文句柄是持久句柄,因此退出句柄作用域时不会将其删除。 删除上下文句柄的唯一方法是在其上显式调用Reset。
注意:在本文档中,术语“Handle”是指本地句柄。 在讨论持久句柄时,该术语将完整使用。
注意此模型的一个常见陷阱很重要:您不能直接从声明句柄作用域的函数中返回本地句柄。 如果您执行本地句柄,则尝试返回的结果最终会在函数返回之前被句柄作用域的析构函数删除。 返回本地句柄的正确方法是构造一个EscapableHandleScope而不是HandleScope并在句柄范围上调用Escape方法,并传入要返回其值的句柄。 这是一个在实际中如何工作的示例:
// This function returns a new array with three elements, x, y, and z. Local<Array> NewPointArray(int x, int y, int z) { v8::Isolate* isolate = v8::Isolate::GetCurrent(); // We will be creating temporary handles so we use a handle scope. v8::EscapableHandleScope handle_scope(isolate); // Create a new empty array. v8::Local<v8::Array> array = v8::Array::New(isolate, 3); // Return an empty result if there was an error creating the array. if (array.IsEmpty()) return v8::Local<v8::Array>(); // Fill out the values array->Set(0, Integer::New(isolate, x)); array->Set(1, Integer::New(isolate, y)); array->Set(2, Integer::New(isolate, z)); // Return the value through Escape. return handle_scope.Escape(array); }
Escape方法将其参数的值复制到封闭范围内,删除其所有本地句柄,然后返回可以安全返回的新句柄副本。