认识 V8 引擎( 五 )


内嵌缓存的大致思路就是将初次查找的隐藏类和偏移值保存起来,当下次查找的时候,先比较当前对象是否是之前的隐藏类,如果是的话,直接使用之前的缓存结果,减少再次查找表的时间 。当然,如果一个对象有多个属性,那么缓存失误的概率就会提高,因为某个属性的类型变化之后,对象的隐藏类也会变化,就与之前的缓存不一致,需要重新使用以前的方式查找哈希表 。
2.5.内存管理Node中通过JavaScript使用内存时就会发现只能使用部分内存(64位系统下约为1.4 GB,32位系统下约为0.7 GB),其深层原因是 V8 垃圾回收机制的限制所致(如果可使用内存太大,V8在进行垃圾回收时需耗费更多的资源和时间,严重影响JS的执行效率) 。下面对内存管理进行介绍 。
内存的管理主要由分配和回收两个部分构成 。V8的内存划分如下:

  • Zone:管理小块内存 。其先自己申请一块内存,然后管理和分配一些小内存,当一块小内存被分配之后,不能被Zone回收,只能一次性回收Zone分配的所有小内存 。当一个过程需要很多内存,Zone将需要分配大量的内存,却又不能及时回收,会导致内存不足情况 。
  • 堆:管理JavaScript使用的数据、生成的代码、哈希表等 。为方便实现垃圾回收,堆被分为三个部分: 年轻分代:为新创建的对象分配内存空间,经常需要进行垃圾回收 。为方便年轻分代中的内容回收,可再将年轻分代分为两半,一半用来分配,另一半在回收时负责将之前还需要保留的对象复制过来 。年老分代:根据需要将年老的对象、指针、代码等数据保存起来,较少地进行垃圾回收 。大对象:为那些需要使用较多内存对象分配内存,当然同样可能包含数据和代码等分配的内存,一个页面只分配一个对象 。

认识 V8 引擎

文章插图
 
垃圾回收V8 使用了分代和大数据的内存分配,在回收内存时使用精简整理的算法标记未引用的对象,然后消除没有标记的对象,最后整理和压缩那些还未保存的对象,即可完成垃圾回收 。
在V8中,使用较多的是年轻分代和年老分代 。年轻分代中的对象垃圾回收主要通过Scavenge算法进行垃圾回收 。在Scavenge的具体实现中,主要采用了Cheney算法:通过复制的方式实现的垃圾回收算法 。它将堆内存分为两个 semispace,一个处于使用中(From空间),另一个处于闲置状态(To空间) 。当分配对象时,先是在From空间中进行分配 。当开始进行垃圾回收时,会检查From空间中的存活对象,这些存活对象将被复制到To空间中,而非存活对象占用的空间将会被释放 。完成复制后,From空间和To空间的角色发生对换 。在垃圾回收的过程中,就是通过将存活对象在两个 semispace 空间之间进行复制 。年轻分代中的对象有机会晋升为年老分代,条件主要有两个:一个是对象是否经历过Scavenge回收,一个是To空间的内存占用比超过限制 。
对于年老分代中的对象,由于存活对象占较大比重,再采用上面的方式会有两个问题:一个是存活对象较多,复制存活对象的效率将会很低;另一个问题依然是浪费一半空间的问题 。为此,V8在年老分代中主要采用了Mark-Sweep(标记清除)标记清除和Mark-Compact(标记整理)相结合的方式进行垃圾回收 。
2.6.快照在V8引擎启动时,需要构建JavaScript运行环境,需要加载很多内置对象,同时也需要建立内置的函数,如Array,String,Math等 。为了使V8更加整洁,加载对象和建立函数等任务都是使用JavaScript文件来实现的,V8引擎负责提供机制来支持,就是在编译和执行JavaScript前先加载这些文件 。
V8引擎需要编译和执行这些内置的JavaScript代码,同时使用堆等来保存执行过程中创建的对象、代码等,这些都需要时间 。为此,V8引入了快照机制 。将这些内置的对象和函数加载之后的内存保存并序列化 。序列化之后的结果很容易反序列化,经过快照机制的启动时间可以缩减几毫秒 。快照机制也可以将一些开发者认为需要的JavaScript文件序列化,以减少处理时间 。不过快照机制的加载的代码不能被CrankShaft这样的编译器优化,可能会存在性能问题 。
3.V8 VS JavaScriptCoreJavaScriptCore引擎是WebKit中默认的JavaScript引擎,也是苹果开源的一个项目,应用较为广泛 。最初,性能不是很好,从2008年开始了一系列的优化,重新实现了编译器和字节码解释器,使得引擎的性能有较大的提升 。随后内嵌缓存、基于正则表达式的JIT、简单的JIT及字节码解释器等技术引入进来,JavaScriptCore引擎也在不断的迭代和发展 。


推荐阅读