回顾,前文由node.js写的基于addon的hello world例子
1
2
3
4
5
6
7
8
9
10
11
12 1#include <node.h>
2#include <v8.h>
3using namespace v8; Handle<Value> Method(const Arguments& args) {
4 HandleScope scope;
5 return scope.Close(String::New("world"));
6}
7void init(Handle<Object> exports) {
8 exports->Set(String::NewSymbol("hello"),
9 FunctionTemplate::New(Method)->GetFunction());
10}
11NODE_MODULE(hello, init)
12
HandleScope scope这是什么玩意,哪里才能获取到它的介绍。请留意代码段中using namespace v8 ,实际上它调用了google v8的库,所以要了解node.js addon需要了解google v8引擎(以下简称v8)。
v8 是一个javascript解释引擎,跟传统javascript引擎不同,主要有四个特性
- 快速的属性访问
- 动态机器码生成
- 高效的垃圾收集
- 内联缓存机制
JIT编译(Just-in-time Compile)
以java为例子,java编译器会先将原始代码转换成语法树,语法树编译成虚拟中间语言(字节码),java vm按顺序解释字节码,解析成机器语言。
v8编译流程,编译器将原始代码转换成语法树后,直接解释成机器语言。
传统编译流程
1
2 1代码段 -> 语法树 -> 中间码 -> 机器语言
2
v8编译流程
1
2 1代码段 -> 语法树 -> 机器语言
2
高效内存管理
采用精确分代垃圾收集技术(Precise Generational GC)
年轻代,是一个较小的连续地址空间,经常进行收集。
年长代,被分成几个空间,不经常收集,先在年轻代里创立对象,没有被收集的对象被移到年长代
- 可执行的代码空间
- 存放隐藏类空间
- 大的对象空间 (>8k)
- 数据对象空间 (存放不含指针的对象)
- 普通对象空间
垃圾收集技术
-
清除收集
-
只在年轻代使用复制收集算法
- 间隙时间 2ms
-
全堆非压缩收集
-
对两代使用标记-清除收集算法
- 空闲内存加入空闲列表
- 可能产生碎片
- 间隙时间 50ms
-
全堆压缩收集
-
对两袋使用标记-压缩收集算法
- 间隙时间 100ms
隐藏类
传统的javascirpt引擎使用哈希表hash table来存取属性和方法,每次存取属性或找方法的时候,就会使用字符串作为寻找对象的哈希键key,搜寻哈希表是一个连续动作,首先通过散列(hashing)值判断数据内的位置,然后判断数组内的键值是否相等,如果不相等,位移到其他数组,方法比较费时。
相反,对于C++和JAVA等,编译的时候,会事先知道存储数据的类型和偏移量。
因此,V8通过隐藏类来简化索引,V8在定义对象的过程中,自动创建隐藏类,通过类的数组索引来查找对象的值。详细过程可以参考
内联缓存机制
当进行隐藏类的行为操作的时候,会被缓存,下一次采用相同的隐藏类行为的时候,能直接命中。
基于Google V8的Hello world
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 1#include <v8.h>
2using namespace v8;
3int main(int argc, char* argv[]) {
4 // Get the default Isolate created at startup.
5 Isolate* isolate = Isolate::GetCurrent();
6 // Create a stack-allocated handle scope.
7 HandleScope handle_scope(isolate);
8 // Create a new context.
9 Handle<Context> context = Context::New(isolate);
10 // Here's how you could create a Persistent handle to the context, if needed.
11 Persistent<Context> persistent_context(isolate, context);
12
13 // Enter the created context for compiling and
14 // running the hello world script.
15 Context::Scope context_scope(context);
16 // Create a string containing the JavaScript source code.
17 Handle<String> source = String::New("'Hello' + ', World!'");
18 // Compile the source code.
19 Handle<Script> script = Script::Compile(source);
20
21 // Run the script to get the result.
22 Handle<Value> result = script->Run();
23
24 // The persistent handle needs to be eventually disposed.
25 persistent_context.Dispose();
26 // Convert the result to an ASCII string and print it.
27 String::AsciiValue ascii(result);
28 printf("%s\n", *ascii);
29 return 0;
30}
31
Isolate
Isolate表示一个独立的v8引擎实例,每个实例维护不同的状态。一个Isolate中的对象不能在其他Isolate中使用。当v8被初始化的时候,一个默认isolate被默认创建。开发者可以通过创建额外的Isolate在多线程环境下并行使用。一个Isolate任意时间只允许一个线程在其中运行,可以使用Locker和Unlocker来进行多个线程对一个Isolate的同步。
Context
V8允许不同的JavaScript代码运行在完全不同的环境下,其运行环境称为Context。不同的Context下拥有自己的全局对象(PersistentHandle),运行代码时必须指定所在的Context。最典型的例子就是Chrome的标签,每个标签都拥有自己的Context。
Context拥有自己的全局代理对象(global proxy object),每个Context下的全局对象都是这个全局代理对象的属性。通过Context::Global ()可以得到这个全局代理对象。新建Context时你可以手动指定它的全局代理对象,这样每个Context都会自动拥有一些全局对象,比如DOM。
Context也是一种scope,通过Context::Enter ()和Context::Exit ()来进入、退出,或者使用类似于HandleScope的Context::Scope来隐式进入。
Handle
V8里使用Handle类型来托管 JavaScript对象,与C++的std::shared_pointer类似,Handle类型间的赋值均是直接传递对象引用,但不同的是,V8使用自己的GC来管理对象生命周期,而不是智能指针常用的引用计数。如果一个v8对象没有任何Handle与之相关联(不再被访问),那么这个对象很快就会被垃圾回收器回收掉。
Handle有两种类型,Local Handle和Persistent Handle,类型分别是Local : Handle和Persistent : Handle,前者和Handle没有区别,生存周期都在scope内。而后者的生命周期脱离scope,你需要手动调用Persistent::Dispose结束其生命周期。也就是说Local Handle相当于在C++在栈上分配对象,而Persistent Handle相当于C++在堆上分配对象。
HandleScope
一个函数中,可以有很多Handle,而HandleScope则相当于用来装Handle(Local)的容器,当HandleScope生命周期结束的时候,Handle也将会被释放,会引起Heap中对象引用的更新。HandleScope是分配在栈上,不能通过New的方式进行创建。对于同一个作用域内可以有多个HandleScope,新的HandleScope将会覆盖上一个HandleScope,并对Local Handle进行管理。
参考资料
https://developers.google.com/v8/intro
http://blog.pluskid.org/?p=186