前端技术专家面试-Javascript执行引擎V8&JSCore

124 阅读6分钟

本内容不严谨,是为面试准备的。

=========

更新:

  • V1.1 添加JSCore的内容

=========


核心能力:对虚拟机的理解。这一部分内容能考查出对操作系统、编译、虚拟机相关的理解。

第一部分:V8

一、V8的主要功能

执行JS代码、管理内存分配、内存回收(垃圾回收)、优化代码执行。

二、执行JS代码

  1. 执行JavaScript代码:脚本 -> 词法分析 -> 语法分析 -> AST -> 字节码 -> 解释执行(可以简单理解为字节码边解释边执行)。
  2. 在解释执行的过程中运用了JIT实时优化(热点代码直接变异成机器码)。

三、V8的内存模型

这部分是虚拟机的基础内容。 image.png 这张图片确实展示了V8 JavaScript引擎的内存模型,内容大体上是正确的。

  1. Stack (栈): 用于存储局部变量和函数调用信息。
  2. Heap memory (堆内存),用于存储对象和动态分配的数据。它包含以下几个主要部分:
    1. New space (Young generation) - 新生代空间:
      • Semi space: 两个半空间,用于新生代对象的快速分配和垃圾回收。
    2. Old space (Old generation) - 老生代空间:
      • Old pointer space: 存储包含指针的老年代对象。
      • Old data space: 存储只包含数据(无指针)的老年代对象。
    3. Large object space: 存储超过一定大小的大对象。
    4. Code space: 存储即时编译(JIT)生成的机器码。
    5. Cell space: 存储小的元数据对象。
    6. Property cell space: 存储命名属性的元数据。
    7. Map space: 存储对象的隐藏类(Hidden Class)信息。

四、V8如何进行内存分配

const a = {a : 1}

V8首先在新生代空间(New Space)的一个半空间(Semi-space)中分配内存。 如果对象很大(超过一定阈值),则直接在大对象空间(Large Object Space)分配。

这个过程是很复杂的,知道这么多就可以了

五、内存回收(垃圾回收)

1. 内存回收就是要回收V8分配的且没有用了的内存,如果内存不回收,内存终将被耗尽。

2. 核心衡量指标:回收的效率,不能说回收导致JS停止执行了很长时间;一般都是一边执行JS代码,一边进行回收。

3. 垃圾回收的假设

  • 假设1: 大多数对象在创建后很快就会变得不可访问。
    • 设计影响1:这是分代垃圾回收的核心假设。
    • 设计影响2:新生代使用Scavenge算法,频繁地对年轻对象进行垃圾回收。
    • 设计影响3:只有经过多次垃圾回收仍然存活的对象才会被提升到老生代。
  • 假设2:相关的对象通常会被一起分配和释放(空间局部性原理)。
    • 设计影响1:新生代的复制式回收可以将存活对象紧密地排列在一起。
    • 设计影响2:老生代的标记-整理算法也会将存活对象移动到一起,提高缓存命中率。
  • 假设3: 大多数对象引用指向同代或更年轻的对象。
    • 设计影响1:使用写屏障和记忆集来记录老生代对象对新生代对象的引用。
    • 设计影响2:这样可以避免在新生代垃圾回收时扫描整个老生代。
  • 假设4:大对象的分配相对较少,但会长期存在。
    • 设计影响1:设立专门的大对象空间。
    • 设计影响2:大对象直接在老生代分配,避免在新生代和老生代之间的复制开销。
  • 假设5:用户对短暂但频繁的小暂停的容忍度高于长时间的大暂停。
    • 设计影响1:采用增量和并发垃圾回收技术。
    • 设计影响2:将垃圾回收工作分散到多个小的时间片中。

4. 垃圾回收的算法:

V8引擎使用多种垃圾回收算法来管理内存,主要分为新生代和老生代两个区域。

  1. 新生代垃圾回收(Minor GC):
    1. Scavenge算法:使用半空间(Semi-space)复制方法; 将存活对象从From空间复制到To空间;交换From和To空间的角色;高效处理短生命周期对象;速度快,但空间利用率只有50%。
    2. 对象晋升:经过多次Scavenge仍存活的对象晋升到老生代;对象大小超过25%的Semi-space大小时直接晋升。
  2. 老生代垃圾回收(Major GC):
    1. 标记-清除(Mark-Sweep)算法:
      • 标记阶段:从根对象开始,标记所有可达对象。
      • 清除阶段:清除未被标记的对象。
      • 优点是不移动对象,缺点是可能产生内存碎片。
    2. 标记-压缩(Mark-Compact)算法:
      • 在标记-清除基础上增加压缩步骤。
      • 将存活对象移动到内存的一端,消除碎片。
      • 提高内存利用率,但速度较慢。
    3. 增量标记(Incremental Marking):
      • 将标记过程分解成多个小步骤。
      • 穿插在JavaScript执行过程中进行。
      • 减少主线程的停顿时间。
    4. 并发标记(Concurrent Marking):
      • 在后台线程中并发执行标记操作。
      • 进一步减少主线程停顿。
    5. 惰性清理(Lazy Sweeping):
      • 延迟清理操作,按需进行。
      • 减少每次GC的工作量。
  3. 大对象空间垃圾回收:
    • 使用特殊的算法处理大对象。
    • 通常采用标记-清除策略,避免移动大对象。
  4. 垃圾回收器选择:
    • V8根据堆的大小和GC频率动态选择最适合的GC算法。
    • 小堆倾向于使用Scavenge,大堆更多使用标记-压缩。
  5. 并行和并发技术:
    • 并行标记和清理:利用多核处理器加速GC过程。
    • 并发清理:在JavaScript执行的同时进行清理操作。

第二部分:JavascriptCore

  1. 用于iOS和macOS平台,主要服务于Safari和iOS的WebView(WKWebView)。它在苹果生态内的应用非常广泛,适合与Objective-C和Swift集成。
  2. 有JIT编译,但由于苹果的安全和资源管控,iOS中的JavaScriptCore在WebView中通常不启用JIT(特别在WKWebView上),这会限制其在iOS上的执行速度。JavaScriptCore更注重资源优化,以减少在内存和电量方面的消耗。
  3. 提供了简洁的Objective-C和Swift接口,可以直接调用JavaScript代码并在JavaScript和原生代码之间传递数据,这是iOS App在WebView中嵌入JavaScript的主要方式。
  4. 支持Safari的开发者工具,允许在iOS上调试WebView内容,查看内存消耗、网络请求等。但Safari的调试工具对一些高级性能分析功能的支持有限。
  5. 同样具备垃圾回收机制,但在iOS平台上的WebView中,回收频率、资源使用受到系统严格限制,以延长电池寿命和提升稳定性。
  6. 同样支持现代JavaScript标准,但在iOS平台上可能会稍有滞后。Safari更新后,JavaScriptCore才会更新对应的特性。

第三部分:一些坑

  1. 在IOS上如果变量没有定义直接引用会报错;在Android上则不会报错。