A闪的 BLOG 技术与人文
使用V8来编程时,必不可少要用到的几个概念就是HandleScope, Local, Persistent。简单来说,HandleScope表示JS对象的生命周期的范围,Local创建一个指向JS对象的本地引用,而Persistent则创建一个指向JS变量的持久引用。所谓本地引用,就是指Local引用不能超出HandleScope的范围;所谓持久引用,即Persistent引用不受HandleScope的影响。
Handle,简单的说,是对一个特定JS对象的索引。它指向此JS对象在V8所管理的Heap中的位置。需要注意的是,Handle不存于Heap中,而是存在于stack中。只有一个Handle被释放后,此Handle才会从stack中推出。这就带来一个问题,在执行特定操作时,我们可能需要声明很多Handle。如果要一个个手动释放,未免太麻烦。为此,我们使用Handle Scope来集中释放这些Handle。
Handle Scope,形象的说是一个可以包含很多Handle的工作区。当这个工作区Handle Scope被移出堆栈时,其所包含的所有Handle都会被移出堆栈,并且被垃圾管理器标注,从而在后续的垃圾回收过程快速的定位到这些可能需要被销毁的Handle。
HandleScope 是用来装 Handle 的容器,如果在代码中未创建 HandleScope ,则当运行发现 Handle 时V8会报错。
当HandleScope生命周期结束的时候,Handle也将会被释放。
HandleScope是分配在栈上,不能通过直接New的方式进行创建。对于同一个作用域内可以有多个HandleScope,新的HandleScope将会覆盖上一个HandleScope,并对Local Handle进行管理。
理论上,只要内存足够,HandleScope 中可容纳句柄的数量是无限的。 HandleScope 包含了对 Handle 的内存控制,部分和GC有很打关联
创建
v8::HandleScope handle_scope(isolate);
HandleScope在使用的时候必须绑定一个Isolate,因为Isolate里管理内存相关信息。
EscapableHandleScope
除了HandleScope外,还有一个EscapableHandleScope。所有处于HandleScope范围内的Local引用,一旦HandleScope失效了,对应的引用也处于不确定状态。
假如开发者希望保留一个有用的最终结果,但是忽略掉中间引用的话,EscapableHandleScope就会非常有用。 比如,你希望从一个Object中获取一个属性,这需要3个Local引用:Object对象的引用,Key的引用(假设使用字符串作为key)和最终的Value的引用。我们希望保留Value的引用,但是Key和Object的引用不再保留,那么EscapableHandleScope就是最好的手段了。
EscapableHandleScope的原理非常简单:它继承自HandleScope,在HandleScope记录当前Block地址和Slot地址之前,预先保留一个Slot作为需要逃逸的引用地址。EscapableHandleScope只能逃逸一个。本质上,它只是一个简单的辅助类而已。
SealHandleScope
SealHandleScope的作用类似于 不允许分配句柄的HandleScope。它可以用于调试句柄泄漏。句柄可在内部的普通 HandleScope中分配。
句柄就是做V8开发的重点核心的概念。我们接入的接口全部都由句柄来实现。即Local和Persistent管理的并不是JS对象本身,而是JS对象的引用。在V8内,JS对象的实际类型是 v8::internal::Object*
,而 Local/Persistent 管理的是 v8::internal::Object**
。
句柄根据其生命周期的差异分为几类,在使用时需要注意。 句柄的实现均是以泛型模板的方式实现,这种方式可以配合不同的数据类型使用。
PersistentBase全局性句柄中根据管理所有权方式的不同又分为两个子类型。 - Persistent:采用赋值和复制方式 - Global:采用移动语义,注意它拥有一个模块类实现的别名,叫做 UniquePersistent
Local,Eternal和PersistentBase之间没有共基类的关系。
要掌握句柄的使用方法必定涉及到V8的数据类型,数据类型概念相对独立,此处可先跳转到“V8数据类型” 章节了解相关技术。 为了方便简单理解,在后面的内容中,均使用 v8::String 来作为讲解.
Local是v8中存储对象的结构,代表了被GC管理的对象引用。简单的可以理解为,我们在JS中创建的对象,在c++层,都能找到对应的Local引用,而每个对象因为类型不同,Local中的类型也不同。
以下代码中,在C++层定义一个字符串类型。
v8::Local<v8::String> str = v8::String::NewFromUtf8(isolate, "'Hello");
并非在任意时刻都能创建任意类型的句柄。这些创建条件取决于对应的类型内存分配条件。
例如,创建 v8::Local<v8::String>
时,可以在 Isolate 创建后, Context 创建前 这个阶段。这是因为 v8::String 表示原始字符串值。无需调用特定于上下文的构造函数即可创建它。
而当你创建 v8::Array 时,则必须 在创建 Context 之后才会正常执行。这是因为 V8实际上会从当前Context 上下文中调用Array的构造函数。如果没有上下文,那么V8将无法调用以创建一个数组,并将导致 segmentation fault 错误。
为了保险起见,最好将所有的句柄创建都放到V8环境创建完毕之后。
MaybeLocal<>是一个围绕Local<>的包装器,它强制检查Local<>在可以使用之前是否为空。
如果API方法返回MaybeLocal<>,则该API方法可能会失败,原因可能是引发了异常,或者异常处于挂起状态,例如,前一个API调用引发了尚未捕获的异常,或者引发了TerminateExecution异常。在这种情况下,返回一个空的MaybeLocal。
如果拿到了一个MaybeLocal 结果,可以调用 ToLocalChecked() 转换为 Local。如果此MaybeLocal<>为空,V8将进程崩溃。
永久句柄生命周期是跟着 Isolate 共生存的。创建方式如下:
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
v8::Eternal<v8::FunctionTemplate> eternal(isolate, t);
也可以先新建一个,然后再调用Set进行设置。
v8::Eternal<v8::FunctionTemplate> eternal();
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
eternal->(isolate, t);
持久型句柄,基类为 PersistentBase ,这里将统一讨论它和它子类的差异。当涉及到持久型句柄时,应该默认为是 PersistentBase 的行为。 Local 句柄生命周期受到 HandleScope 控制,而 持久型句柄不受任何 HandleScope 生命周期控制。如果想释放一个 持久型句柄 ,则需要调用它的 Reset() 方法。
持久型句柄 是对V8引擎中存储数据的引用,当GC执行时,它的引用也会跟随移动。
弱引用
一旦使用了持久型句柄,则对应在V8堆中的数据就不会被GC回收。除非我们手动调用 Reset() 方法。但还可以设置其成为弱引用,一旦被标记为弱引用,则GC在检测该对象时,其引用计数则会忽略它。当GC检测到该对象不可被访问时,会自动执行 reset。
SetWeak () 和 ClearWeak () 可以设置或取消设置句柄成为弱引用。
Persistent变量被v8的Isolate记录,会在对象被回收后自动置空该对象。这里需要非常注意一点:Persistent这个C++对象不能在Isolate dispose之前被删除,否则就会导致非法指针。
弱引用回调
设置为弱引用还有另外一个接口,可以在GC处理不可访问时,进行回调。但是并不保证回调的时间和时机。 在回调中,不能调用V8代码,如果需要,可以设置二次回调。API原型如下:
void v8::PersistentBase< T >::SetWeak(P * parameter,
typename WeakCallbackInfo< P >::Callback callback,
WeakCallbackType type
)
WeakCallbackType的定义如下:
回调函数可如下定义:
void SecondCallBack const v8::WeakCallbackInfo<v8::Value>& data){
//...
}
void Callback(const v8::WeakCallbackInfo<v8::Value>& data){
//...
//如果要进行二次回调,参考下面代码
data->SetSecondPassCallback( SecondCallBack );
}
AnnotateStrongRetainer
void v8::PersistentBase< T >::AnnotateStrongRetainer(const char * label)
给句柄设置一个 label 标记,主要为堆的快照生成器做标识用。
Persistent 继承自 PersistentBase,它允许赋值和复制。 Persistent是不能直接参与到v8 API的使用,必须先转换为v8::Local,然后才能使用。
Global 继承自 PersistentBase,它具有移动语义。