V8 的垃圾收集过程是其内存管理系统的核心部分,它负责回收那些不再被应用程序使用的内存空间,以避免内存泄漏并提高性能。V8 的垃圾收集过程主要基于分代垃圾收集策略,将内存中的对象分为新生代和老生代,并分别采用不同的算法进行垃圾收集。以下是 V8 垃圾收集过程的具体工作方式:
1. 新生代垃圾收集
新生代是存放新创建对象的内存区域,这些对象往往很快就会被销毁或变得不可达。V8 使用 Scavenge 算法来管理新生代的内存。
Scavenge 算法的工作流程:
- 内存划分:新生代被划分为两个等大的空间,称为 From 空间和 To 空间。新创建的对象会被分配到 From 空间中。
- 垃圾收集触发:当 From 空间被填满时,会触发一次垃圾收集。
- 标记存活对象:垃圾收集器会遍历 From 空间中的所有对象,标记出那些仍然被引用的对象(即存活对象)。
- 复制存活对象:将 From 空间中的存活对象复制到 To 空间中,并维持它们在内存中的相对位置关系。
- 空间角色翻转:完成复制后,From 空间和 To 空间的角色会互换,原来的 To 空间变为新的 From 空间,用于后续新对象的分配;原来的 From 空间(现在已清空)变为新的 To 空间,等待下一次垃圾收集时的对象复制。
对象晋升:如果一个对象在新生代中经过多次垃圾收集后仍然存活,它会被认为是一个长期存活的对象,并会被晋升到老生代中,以便使用更适合长期存活对象的垃圾收集算法。
2. 老生代垃圾收集
老生代是存放存活时间较长的对象的内存区域。V8 使用标记-清除(Mark-Sweep)和标记-整理(Mark-Compact)算法来管理老生代的内存。
标记-清除算法的工作流程:
- 标记阶段:从一组根对象(如全局对象、当前执行栈中的局部变量等)开始,递归遍历这些对象及其引用的其他对象,标记出所有可达的对象(即存活对象)。
- 清除阶段:遍历堆中的所有对象,回收那些未被标记为存活的对象所占用的内存空间。
标记-整理算法的工作流程(可选):
- 在多次执行标记-清除算法后,老生代中可能会产生大量内存碎片,影响后续的内存分配。此时,V8 可以选择执行标记-整理算法来优化内存布局。
- 标记阶段与标记-清除算法相同,但整理阶段会将所有存活对象移动到内存的一端,形成一个连续的内存块,然后清理掉边界以外的内存空间。
3. 增量标记和并行回收
为了减少垃圾收集对应用程序性能的影响,V8 引入了增量标记和并行回收技术。
- 增量标记:将垃圾收集的标记过程分解为多个小步骤,每次只执行一小部分标记工作,然后让应用程序继续执行一段时间。这样可以在不完全停止应用程序的情况下逐步完成垃圾收集的标记阶段。
- 并行回收:在垃圾收集过程中,不仅主线程参与工作,还会利用多个辅助线程来加速标记和清理过程。这可以显著减少垃圾收集所需的总时间。
4. 垃圾收集的影响
需要注意的是,虽然 V8 的垃圾收集器是高度优化的,但在执行垃圾收集时仍然需要暂停应用程序的执行(即所谓的“全停顿”或“Stop-The-World”)。这种停顿虽然通常很短,但在某些情况下仍然可能对应用程序的性能产生影响。因此,开发者在编写 JavaScript 代码时应该尽量避免创建大量短命对象或产生复杂的引用关系,以减少垃圾收集的负担和停顿时间。