渲染,就是根据描述或者定义构建数学模型,生成图像的过程。node
浏览器内核主要的做用是将页面转变成可视化/可听化的多媒体结果,一般也被称为渲染引擎。将HTML/CSS/JavaScript文本及其余相应的媒体类型资源文件转换成网页。算法
上图中实线框内模块是全部移植的共有部分,虚线框内不一样的厂商能够本身实现。下面进行介绍:数据库
WebCore 是各个浏览器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。编程
JavaScriptCore是WebKit的默认引擎,在谷歌系列产品中被替换为V8引擎。数组
WebKit Ports是WebKit中的非共享部分,因为平台差别、第三方库和需求的不一样等缘由,不一样的移植致使了WebKit不一样版本行为不一致,它是不一样浏览器性能和功能差别的关键部分。浏览器
WebKit嵌入式编程接口,供浏览器调用,与移植密切相关,不一样的移植有不一样的接口规范。缓存
浏览器内核的层次结构,渲染引擎解析网页资源,调用第三方库进行渲染绘制。闭包
从上面两图能够大概看出渲染引擎的内部组成和大模块。WebCore,负责解析HTML、CSS生成DOM树等渲染进程,js引擎负责解析和执行js逻辑进程。函数
JavaScript引擎和渲染引擎的关系如上图所示。渲染引擎使用JS引擎的接口来处理逻辑代码并获取结果。JS引擎经过桥接接口访问渲染引擎中的DOM及CSSOM(性能低下)。工具
JavaScript本质上是一种解释型语言,与编译型语言不一样的是它须要一遍执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度(如上图所示)。
上图描述了JS代码执行的过程。具体过程先不看,一个JS引擎主要包括如下几个部分:
编译器。将源代码通过词法分析,语法分析(你不知的js待查)编译成抽象语法树,在某些引擎中还包含将抽象语法树转化成字节码。
解释器。在某些引擎中,解释器主要是接收字节码,解释执行这个字节码,同时也依赖垃圾回收机制等。
JIT工具。将字节码或者抽象语法树转换成本地代码。
垃圾回收器和分析工具(Profiler)。负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。
Js语言中,只有基本数据类型Boolean、Number、String、Null、Undefined,其余都是对象。
在V8中,数据的表示分红两个部分。第一个部分是数据的实际内容,它们是变长的,并且内容的类型也不同,如String、对象等;第二部分是数据的句柄,大小是固定的,包含指向第一部分数据的指针。除了极少数的数据例如整型数据,其余的内容都是从堆中申请内存来存储,由于句柄自己可以存储整型,同时也能快速访问。
V8须要进行垃圾回收,并须要移动这些数据内容,若是直接使用指针的话就会出问题或者须要比较大的开销。使用句柄就不存在这些问题,只须要修改句柄中的指针便可,使用者使用的仍是句柄,它自己没有发生变化。
由上图能够看出,一个Handle对象的大小是4字节(32位机器)或者8字节(64位机器);不一样于JavascriptCore引擎,后者是使用8个字节来表示数据的句柄。小整数(只有31位可使用)直接在Value_中获取值,而无须从堆中分配。
由于堆中存放的对象都是4字节对齐的,因此指向它们的指针的最后两位都是00,这两位实际上是不须要的。在V8中,它们被用来表示句柄中包含数据的类型。
对象句柄的实如今V8中包含3个成员。第一个是隐藏类指针,为对象建立的隐藏类;第二个指向这个对象包含的属性值;第三个指向这个对象包含的元素。
包括两个阶段:编译和执行。还有一个重要的特色就是延迟思想,使得不少js代码的编译直到运行时被调用才会发生(以函数单位),减小时间开销。
Js代码通过编译器,生成抽象语法树,再经过JIT全代码生成器直接生成本地代码。减小抽象树到字节码的转换时间。
生成本地代码后,为了性能考虑,经过 数据分析器 来采集一些信息,以帮助决策哪些本地代码须要优化,以生成效率更高的本地代码,这是一个逐步改进的过程。
编译器会作比较乐观和大胆的预测,就是认为这些代码比较稳定,变量类型不会发生改变,来生成高效的本地代码。当引擎发现一些变量的类型已经发生变化的时候,V8会将优化回滚到以前的通常状况。
如上,函数ABC被调用不少次以后,数据分析器认为函数内的代码的类型都已经被获知了,可是当对于unkonwn的变量发生赋值是,V8只能将代码回滚到一个通用的状态。
优化回滚是一个很费时的操做,并且会将以前优化的代码恢复到一个没有特别优化的代码,这是一个很是不高效的过程。
V8使用类和偏移位置思想,将原本须要字符串匹配来查找属性值的算法改进为,使用相似C++编译器的偏移位置的机制来实现,这就是隐藏类。
隐藏类根据对象拥有相同的属性名和属性值的状况,分为不一样的组(类型)。对于相同的组,将这些属性名和对应的偏移位置保存在一个隐藏类中,组内的对象共享该信息。同时,也能够识别属性不一样的对象。
如图,使用构造函数建立了两个对象a、b。这两个对象包含相同的属性名,在V8中它们被归为同一个组,也就是隐藏类,这些属性在隐藏类中有相同的偏移值。对象a、b能够共享这个分组的信息,当访问这些对象的时候,根据隐藏类的偏移值就能够知道它们的位置并进行访问。
由于JavaScript是动态类型语言,因此当加入代码 d.z = 2。那么b对象所对应的将是一个新的隐藏类,这样a、b将属于不一样的组。
function add(a) { return a.x };
访问对象属性基本过程为:获取隐藏类的地址,根据属性名查找偏移值,计算该属性的堆内存地址。这一过程仍是比较耗费时间,实际上会用到缓存机制,叫作内嵌缓存。
基本思想是将使用以前查找的结果(隐藏类和偏移值)缓存起来,再次访问时能够避免屡次哈希表查找的问题。
注意:当对象内的属性值出现多个类型是,那么缓存失误的几率就会高不少。退回到以前的方式来查找哈希表。
主要讲两点:一、内存的划分使用二、对于JS代码的垃圾回收机制
管理一系列的小块内存,这些小内存的生命周期相似,可使用一个Zone对象。
Zone对象先对本身申请一块内存,而后管理和分配一些小内存。当一块小内存被分配以后,不能被Zone回收,只能一次性回收Zone分配的全部小内存。例如:抽象语法树的内存分配和使用,在构建以后,会生成本地代码,而后其内存被一次性所有收回,效率很是高。
可是有一个严重的缺陷,当一个过程须要不少内存,Zone将须要分配大量的内存,却又不能及时回收,会致使内存不足状况。
V8使用堆来管理JavaScript使用的数据、以及生成的代码、哈希表等。为了更方便地实现垃圾回收,同不少虚拟机同样,V8将堆分红三个部分。年轻代、年老代、和大对象。
年轻分代:为新建立的对象分配内存空间,常常须要进行垃圾回收。为方便年轻分代中的内容回收,可再将年轻分代分为两半,一半用来分配,另外一半在回收时负责将以前还须要保留的对象复制过来。
年老分代:根据须要将年老的对象、指针、代码等数据保存起来,较少地进行垃圾回收。
大对象:为那些须要使用较多内存对象分配内存,固然一样可能包含数据和代码等分配的内存,一个页面只分配一个对象。
当在代码中声明变量并赋值时,所使用对象的内存就分配在堆中。若是已申请的堆空闲内存不够分配新的对象,将继续申请堆内存,知道堆的大小达到V8的限制为止。
V8的内存使用限制:64位系统中约为1.4G,32位系统中约为0.7G。在浏览器页面中足够使用;在node中则会有不足,致使Node没法直接操做大内存对象,在打个node进程的状况下,没法充分利用计算机的内存资源。
内存的限制有两方面缘由,防止浏览器的一个页面占用太多系统内存资源,另外一方面,V8垃圾回收的效率问题。对于1.5G内存,作一次小的垃圾回收须要50毫秒以上,作一次全量的回收甚至要1秒以上,这个过程会阻塞JS线程的执行。
V8的垃圾回收策略主要基于分代式回收机制。按对象的存活时间将内存的垃圾回收进行不一样的分代,而后分别对不一样的内存使用更高效的算法。
一、新生代Scavenge(清除)算法
主要采用Cheney(人名)算法。一种采用复制的方式实现的垃圾回收算法。
一、将新生代堆内存分一为二,每一部分空间称为semispace。其中一个处于使用之中的称为from空间,另外一个处于闲置称为to空间。
二、当咱们分配对象时,先是在From空间中进行分配。
三、垃圾回收时,检查from空间内的存活对象,一是否经历过清除回收,二to空间是否已经使用了25%(保证新分配有足够的空间)。
. 四、将这些存活对象复制到to空间中。非存活对象占用的空间将会被释放。
五、完成复制后,from空间与to空间角色发生对换。
注:实际使用的堆内存是新生代中的两个semispace空间大小,和老生代所用内存大小之和。
如何判断对象是否存活呢?做用域?是一套存储和查询变量的规则。这套规则决定了内存里对象可否访问。
特色:
清除算法是典型的牺牲空间换取时间的算法,没法大规模地应用到全部回收中,却很是适合应用在新生代生命周期短的变量。
一、 标记阶段遍历堆中的全部对象,并标记活着的对象
二、 清除阶段,只清除没有被标记的对象。
最大的问题是,在进行一次标记清除以后会出现不连续的状态。这种内存碎片会对后续的内存分配形成问题。极可能须要分配一个大对象时,全部的碎片空间都没法完成,就会提早触发垃圾回收,而此次全量回收是没必要要的。
在标记清除的基础上发展而来,在整理的过程当中
一、 将活着的对象往一段移动
二、 移动完成后,直接清理掉边界外的内存
垃圾回收的过程都须要将应用逻辑暂停下来。
为了下降全量回收带来的停顿时间,在标记阶段,将本来一口气要完成的动做改成增量标记。垃圾回收与应用逻辑交替执行到标记阶段完成。最大停顿时间较少的1/6左右.
后续还引入了延迟清理与增量整理,让清理和整理动做也变成增量式的。
函数在每次被调用时会建立对应的做用域(自身的执行环境,和变量对象),做用域中声明的局部变量分配在改做用域中。执行结束后,做用域销毁,作死亡标记,在下次垃圾回收时被释放。
变量的主动释放,解除引用关系:
若是变量是全局变量,因为全局做用域须要直到进程退出才能释放,致使引用的对象常驻内存(老生代)。能够经过delete来删除引用关系,或者将变量从新赋值。可是在V8中经过delete删除对象的属性有可能干扰V8的优化。
闭包:
当函数执行结束,但做用域(变量对象)仍须要被引用时,其变量对象不能被标记失效,占用的内存空间不会获得清除释放。除非再也不有引用,才会逐步释放。
在正常的js执行中,没法当即回收的内存有闭包和全局变量这两种状况。要避免这些变量无限制地增长,致使老生代中的对象增多,甚至内存泄漏。
实质:应当回收的对象出现意外,而没有被回收,变成了常驻在老生代中的对象。
一般,形成内存泄漏的缘由有以下:
一、 缓存
缓存无限制增加,长期存在于老生代,占据大量内存。
策略:对缓存增长数量和有效期限制。
使用Redis等进程外缓存,不占用V8缓存限制,进程间能够共享缓存。
二、 队列消费不及时
Js能够经过队列(数组对象)来完成许多特殊的需求。在消费者—生产者模型中常常充当中间产物。当消费速度低于生成速度时,将会造成堆积。
如:日志记录,采用低效率的数据库写入时,可能会是写入事件堆积。
策略:使用高效的消费方式。监控队列的长度。
三、 做用域得不到释放
大量闭包的使用,要注意释放做用域。