1 JS语言与性能问题
JS比Java慢的原因
和大多数解释型语言一样,JavaScript运行也比较慢,和Java等静态编译语言相比,究其原因大概有
- JS变量无类型信息,不能做偏移信息查找,偏移信息共享等编译阶段的优化
- JS将源码编译为字节码的过程要占用运行时间,而Java的编译则是开发阶段,不占用任何运行时间。故Java可以尽可能的在编译阶段做优化
JS引擎组成
一个JS引擎大概包含以下几个部分。
- 编译器:将源码转变为抽象语法树,某些引擎还将语法树转变为了字节码。编译过程会占用用户时间。
- 解释器:接收字节码,执行它。需要处理内存分配,动态优化等
- JIT:运行时优化,热点代码字节码转变为本地代码。和Java中的JIT类似。如果优化没有达到效果,还会回退为优化前代码。
- 垃圾回收器GC和分析工具,分析工具收集运行时信息,如热点代码探测。
2 V8引擎
数据表示
数据的表示有两部分。数据实际内容,变长的,如String,Object等。 数据句柄,大小固定,包含指向数据的指针。这样设计的原因是,垃圾回收碎片处理时,一般需要移动数据来形成连续的一块可用内存。使用句柄可以通过修改句柄中指针即可,而数据本身不用改变。我们引用这个数据时,通过句柄即可。对于整形数据,直接放在放在句柄中。而对于其他数据,句柄保存的都只是他们的指针而已,真实内存需要从堆中去申请。
工作过程
大概分为编译和运行两个阶段。编译发生在用户使用时,会占用用户时间,这一点和静态语言完全不同。运行到的代码才会进行编译,不需要所有代码都编译,这样可以减少编译开销。
V8编译大概分为两个过程
- 使用Parser将源代码转变为抽象语法树
- 使用FullCodeGenerator生成本地代码,这一点和Webkit默认的JavaScriptCore差别较大,它是生成中间字节码。
编译完成后,就可以执行代码了。大概分为下面几个步骤
- 找到入口,调用本地代码进入
- 为变量申请和分配内存
- 执行本地代码,完成后返回结果
内存管理
内存管理一直是一个语言执行引擎的重中之重。V8引擎将内存划分为两个部分。
Zone:管理一系列小块内存。小内存都是从Zone这个对象中申请的。刚开始时,Zone自身会去申请一块内存。然后再在它里面申请小块内存。只能一次性回收Zone分配的所有小块内存,不能单独一个个回收。故Zone中不适合存放比较大的内存,它们释放不掉会给系统带来很大压力
-
堆:JS使用的数据,生成的代码,hash表等都放在堆中,和Java一样,也是采用了分代策略。分为三个部分
- 年轻代:释放比较频繁,采用复制清除法回收
- 老年代:久经考验而未释放的对象,采用标记整理法回收
- 大对象
Snapshot
为了优化初始加载的各种内置对象(如window Navigator),内置函数(如parseInt())。V8将它们加载之后的内存保存并序列化到磁盘中,下次要使用时直接从磁盘中反序列化到内存中。
3 JavaScriptCore引擎
它是Webkit的默认JS引擎。与V8一样,也是采用句柄来表示数据对象。对热点代码,在运行阶段会由JIT将字节码编译为本地代码。内存管理方面,也同样采用分代策略。
不同点在于,编译阶段不是从抽象语法树生成本地代码,而是生成平台无关的字节码。故此后可以不需要源代码了,而v8由于要做优化,必须从源代码下手优化。