垃圾回收
一、认识
JavaScript
是一门自动管理内存的语言,开发者不需要手动分配和释放内存。垃圾回收(Garbage Collection
,GC
)就是在后台自动检测不再被引用的对象,然后释放它们占用的内存,从而避免内存泄漏和程序崩溃。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
采用了三种主要的优化策略:-
并行回收(
Parallel
), 利用多个线程同时执行GC
操作,缩短整体回收时间。 -
增量回收(
Incremental
), 将一个完整的标记过程拆分成多个小阶段,在主线程任务之间穿插执行,减少一次性停顿时间。 -
并发回收(
Concurrent
), 后台并发地执行标记或清理工作,同时主线程继续运行,这样可以降低对用户体验的影响。
-