跳到主要内容

垃圾回收

2024年04月07日
柏拉文
越努力,越幸运

一、认识


JavaScript 是一门自动管理内存的语言,开发者不需要手动分配和释放内存。垃圾回收(Garbage CollectionGC)就是在后台自动检测不再被引用的对象,然后释放它们占用的内存,从而避免内存泄漏和程序崩溃。GC 垃圾回收策略如下:

一、古老的垃圾回收策略: 引用计数(Reference Counting: 每个对象维护一个引用计数器,当有变量引用该对象时计数器加一;当引用解除时计数器减一。当计数器为 0 时,说明没有任何引用,垃圾回收器就可以释放该对象的内存。引用计数容易出现循环引用的问题,即两个或多个对象互相引用,即使它们已经不再被外部使用,计数器也不会归零,导致内存泄漏。现代 JavaScript 引擎大多采用分代回收策略。

二、现在主流垃圾回收策略: 分代垃圾回收 Generational GC, 分代垃圾回收下, 不同代采用不同的回收算法的策略。分代回收基于 代际假说 的大多数对象朝生夕死, 少数对象会长期存活的思想, 将堆内存分为分为 新生代老生代。现代引擎会根据内存使用情况、对象分配速率和应用的活动状态等动态判断何时启动 GC,而不是每个对象一旦不再引用就立即回收。

  • 新生代: 空间较小(1-8MB), 基于 Scavenge 算法, 将空间分为两个区域, 对象区域(from-space) 和 空闲区域(to-space), 新对象分配在对象区域, 回收时将存活对象复制到空闲区域, 两个区域角色互换。每次回收时, 将存活对象复制到空闲区域, 经过几次回收后且存活率较高的对象,会被晋升到老生代。优点是回收速度快且几乎没有碎片问题, 但空间较小。

  • 老生代: 空间较大, 主要采用 标记-清除(Mark-Sweep标记-整理(Mark-Compact 算法。标记-清除(Mark-Sweep, 从根节点遍历,标记所有可达对象, 并清除未标记的对象, 但可能会导致内存碎片。标记-整理(Mark-Compact, 从根节点遍历,标记所有可达对象, 在标记后将存活对象整理到内存一端,再清理剩余空间,避免碎片,但整理过程开销较大。

  • 优化策略: 大部分 GC 阶段(如标记和整理)会引发 Stop-the-World 现象,即暂停 JavaScript 执行,但通过并行、增量、并发技术,GC 停顿时间得到了显著优化。为了提高垃圾回收的效率, V8 采用了三种主要的优化策略:

    1. 并行回收(Parallel, 利用多个线程同时执行 GC 操作,缩短整体回收时间。

    2. 增量回收(Incremental, 将一个完整的标记过程拆分成多个小阶段,在主线程任务之间穿插执行,减少一次性停顿时间。

    3. 并发回收(Concurrent, 后台并发地执行标记或清理工作,同时主线程继续运行,这样可以降低对用户体验的影响。