因工作需要,做V8嵌入式开发已经有一段时间了,踩坑无数。好在项目已通过测试和多轮灰度,即将上线,终于有时间静下来梳理梳理。由于网上相关资料甚少,那就先从基础概念介绍起吧。
什么是V8?
本文说的V8是Google开发的一款开源的js引擎,例如大家熟知的Chrome、Nodejs都使用了它来动态解析javascript。它自称性能杠杠的,但在ARM架构上JavascriptCore表示不服,也自称性能比V8高不少。据说Weex在选型之初,UC的小伙伴还给过两个引擎的测试数据,证明JavascriptCore并没有说谎。
Isolate
An isolate is a VM instance with its own heap --- 来自官网的简介。
简单说来isolate就是js的虚拟机,是js最大的运行环境。一般一个线程对应一个isolate,当然也可以多线程操作它,但请记得加锁,不然很容易crash,它并非线程安全的(好像JavascriptCore能保证线程安全)。
Context
Context是js的上下文,A Context中的js变量完全独立于B Context,一般用Context来做js的安全隔离。这里需要区别于Isolate,Context必须依附于Isolate。
我们在做嵌入式开发时,一般有 单引擎多实例 和 单引擎单实例 两种模式。单引擎多实例是指一个Isolate里面跑多个Context,单引擎单实例是一个Isolate只跑一个Context,那这两种模式各有什么优缺点呢?
初始化一个isolate耗时较长、占用内存较高,某些场景我们为了提升启动速度和节省内存,我们会使用单引擎多实例方式。但单引擎多实例的方式一般是在一个线程里面跑,虽然js变量能隔离,但是线程阻塞导致相互影响。比如你在A context里面写了一个耗时任务,那这个任务会阻塞其他context内的js运行。所以需要根据自己的使用场景去合理的搭配才完美。
Handles & HandleScope
熟悉js的同学应该都听过“垃圾回收”,js是由V8自动管理回收内存,具体V8是怎么回收内存的还有点绕,它并不是单纯的标记法或引用计数,感兴趣的可以研究下。
一个handle对应了一个堆上的js native object,HandleScope表示一个管辖范围,位于自己管辖范围的handle的内存由该HandleScope来管理。比如你在创建handle的时候必须在外层显示的说明这个handle是位于哪个HandleScope的“管辖”,这样你就不用操心内存的释放问题了。下图是v8 hello-world的代码:
ScriptCompile & CodeCache
我们先来看一段代码,JavaScript是怎么在V8里面运行起来的。
Step1:拿到js代码字符串
Step2:通过v8::Script::Compile编译代码
Step3:执行run方法
What?js还需要编译?对,你没有看错,而且这里的compile还是整个初始化流程里面最耗时的,这货是启动流程的瓶颈之一。为了优化它,V8后来又推出了code cache,就是把compile的结果保存,下次直接通过上次compile的结果去得到序列化好的script对象,来减少启动耗时,参看下图:
Snapshot
上面提到了启动流程的瓶颈问题,也提到过初始化isolate耗时较高,那明知道这里有问题为啥不解决呢?
别急,V8后来又推出了snapshot的功能,看字面意思就能基本明白,快照。
snapshot可以在运行期对isolate、context拍一个快照,然后存起来。下次启动的时候就直接拿这个快照反序列化出来,直接生成isolate和context,免去了费时的初始化,大大的提升启动速度。
Inspector
Inspector是用于js调试的,它有一套自己的inspect协议,可以把chrome的DevTools作为它的调试客户端,极大的便利了开发者。
PS:以前写的老文章,从知乎 copy 过来