本内容不严谨,是为面试准备的。
=========
更新:
- V1.1 添加JSCore的内容
=========
核心能力:对虚拟机的理解。这一部分内容能考查出对操作系统、编译、虚拟机相关的理解。
第一部分:V8
一、V8的主要功能
执行JS代码、管理内存分配、内存回收(垃圾回收)、优化代码执行。
二、执行JS代码
- 执行JavaScript代码:脚本 -> 词法分析 -> 语法分析 -> AST -> 字节码 -> 解释执行(可以简单理解为字节码边解释边执行)。
- 在解释执行的过程中运用了JIT实时优化(热点代码直接变异成机器码)。
三、V8的内存模型
这部分是虚拟机的基础内容。
这张图片确实展示了V8 JavaScript引擎的内存模型,内容大体上是正确的。
- Stack (栈): 用于存储局部变量和函数调用信息。
- Heap memory (堆内存),用于存储对象和动态分配的数据。它包含以下几个主要部分:
- New space (Young generation) - 新生代空间:
- Semi space: 两个半空间,用于新生代对象的快速分配和垃圾回收。
- Old space (Old generation) - 老生代空间:
- Old pointer space: 存储包含指针的老年代对象。
- Old data space: 存储只包含数据(无指针)的老年代对象。
- Large object space: 存储超过一定大小的大对象。
- Code space: 存储即时编译(JIT)生成的机器码。
- Cell space: 存储小的元数据对象。
- Property cell space: 存储命名属性的元数据。
- Map space: 存储对象的隐藏类(Hidden Class)信息。
- New space (Young generation) - 新生代空间:
四、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引擎使用多种垃圾回收算法来管理内存,主要分为新生代和老生代两个区域。
- 新生代垃圾回收(Minor GC):
- Scavenge算法:使用半空间(Semi-space)复制方法; 将存活对象从From空间复制到To空间;交换From和To空间的角色;高效处理短生命周期对象;速度快,但空间利用率只有50%。
- 对象晋升:经过多次Scavenge仍存活的对象晋升到老生代;对象大小超过25%的Semi-space大小时直接晋升。
- 老生代垃圾回收(Major GC):
- 标记-清除(Mark-Sweep)算法:
- 标记阶段:从根对象开始,标记所有可达对象。
- 清除阶段:清除未被标记的对象。
- 优点是不移动对象,缺点是可能产生内存碎片。
- 标记-压缩(Mark-Compact)算法:
- 在标记-清除基础上增加压缩步骤。
- 将存活对象移动到内存的一端,消除碎片。
- 提高内存利用率,但速度较慢。
- 增量标记(Incremental Marking):
- 将标记过程分解成多个小步骤。
- 穿插在JavaScript执行过程中进行。
- 减少主线程的停顿时间。
- 并发标记(Concurrent Marking):
- 在后台线程中并发执行标记操作。
- 进一步减少主线程停顿。
- 惰性清理(Lazy Sweeping):
- 延迟清理操作,按需进行。
- 减少每次GC的工作量。
- 标记-清除(Mark-Sweep)算法:
- 大对象空间垃圾回收:
- 使用特殊的算法处理大对象。
- 通常采用标记-清除策略,避免移动大对象。
- 垃圾回收器选择:
- V8根据堆的大小和GC频率动态选择最适合的GC算法。
- 小堆倾向于使用Scavenge,大堆更多使用标记-压缩。
- 并行和并发技术:
- 并行标记和清理:利用多核处理器加速GC过程。
- 并发清理:在JavaScript执行的同时进行清理操作。
第二部分:JavascriptCore
- 用于iOS和macOS平台,主要服务于Safari和iOS的WebView(WKWebView)。它在苹果生态内的应用非常广泛,适合与Objective-C和Swift集成。
- 有JIT编译,但由于苹果的安全和资源管控,iOS中的JavaScriptCore在WebView中通常不启用JIT(特别在WKWebView上),这会限制其在iOS上的执行速度。JavaScriptCore更注重资源优化,以减少在内存和电量方面的消耗。
- 提供了简洁的Objective-C和Swift接口,可以直接调用JavaScript代码并在JavaScript和原生代码之间传递数据,这是iOS App在WebView中嵌入JavaScript的主要方式。
- 支持Safari的开发者工具,允许在iOS上调试WebView内容,查看内存消耗、网络请求等。但Safari的调试工具对一些高级性能分析功能的支持有限。
- 同样具备垃圾回收机制,但在iOS平台上的WebView中,回收频率、资源使用受到系统严格限制,以延长电池寿命和提升稳定性。
- 同样支持现代JavaScript标准,但在iOS平台上可能会稍有滞后。Safari更新后,JavaScriptCore才会更新对应的特性。
第三部分:一些坑
- 在IOS上如果变量没有定义直接引用会报错;在Android上则不会报错。