V8老生代默认采用标记-清除(Mark-Sweep)算法:先从GC Roots递归标记可达对象,再清除未标记对象并加入空闲列表;它不移动对象、开销低,可正确处理循环引用,但会产生内存碎片,故常与标记-整理协同工作。

V8 的标记-清除(Mark-Sweep)算法是老生代堆内存回收的主干机制,它不负责“快”,但承担“稳”和“准”——在对象生命周期长、数量大、结构复杂的场景下,确保所有不可达对象被可靠识别并释放。
标记-清除是老生代回收的默认基础流程
当对象从新生代晋升到老生代后,就进入以 Mark-Sweep 为主的管理范围。它的执行分两步:
- 标记阶段:从 GC Roots(如全局对象、栈中局部变量、DOM 引用等)出发,递归遍历所有可到达的对象,将它们标记为“活动”;未被触及的对象即判定为垃圾
- 清除阶段:扫描整个老生代内存空间,回收所有未被标记对象占用的内存,并把空闲块加入空闲列表(free list),供后续 new 对象快速分配
这个过程不移动对象位置,所以执行快、开销低,适合频繁触发的小规模回收。
它解决了引用计数无法处理的关键问题
JavaScript 中常见闭包、事件监听器、双向链表等结构,容易形成循环引用(比如 A → B 且 B → A)。引用计数算法会误判这类对象“仍有引用”,导致内存泄漏;而 Mark-Sweep 基于可达性分析,只关心能否从根出发访问到,天然绕过循环引用陷阱。
但它带来一个必须应对的副作用:内存碎片
因为清除的是离散对象,释放后的空闲内存呈零散分布。例如连续释放三个大小不一的对象后,可能留下三段不相邻的小空隙。若之后需要分配一个中等大小的对象,就可能出现“总空闲够、但没连续块”的情况,触发分配失败或被迫启动更重的整理流程。
因此它常与标记-整理(Mark-Compact)协同工作
当碎片率超过阈值(如 V8 内部监控到空闲块过于细碎),引擎会自动切换到 Mark-Compact:在完成标记后,不是直接清除,而是把所有存活对象向内存一端紧凑排列,再清掉边界外的空间。这虽耗时稍长,却换来干净连续的可用内存,保障后续大对象分配稳定。
本质上,Mark-Sweep 是 V8 老生代回收的“日常主力”,而 Mark-Compact 是它的“后备整容师”。两者配合,既守住回收正确性底线,又兼顾长期运行下的内存健康。

















