v8堆内存分代假设的核心是绝大多数对象“出生即死亡”,仅极少数长期存活;因此分为新生代(小空间、scavenge复制回收)和老生代(大空间、mark-sweep/compact回收),通过晋升机制协同工作。

理解 V8 堆内存的分代假设,关键在于抓住一个核心观察:**绝大多数对象“出生即死亡”,只有极少数能活久一点**。V8 不是统一管理所有对象,而是按“预期寿命”把堆分成两块——新生代(Young Generation)和老生代(Old Generation),再用不同策略配合工作。
新生代:专为“短命对象”设计的快车道
新生代空间小(通常 1–8 MB),只放刚创建的对象,比如函数里声明的局部变量、临时计算结果。它被切成两个等大的半区:From 空间(当前分配区)和 To 空间(空闲复制区)。新对象全扔进 From 空间,一满就触发 Scavenge 回收:
- 扫描 From 空间,找出所有还被引用的存活对象
- 把它们一口气复制到 To 空间(连续存放,天然不碎片)
- 清空 From 空间,然后交换角色——To 变成下一轮的 From
这个过程极快,因为只处理小内存 + 只复制少量存活对象。但它的代价是:每次只能用一半空间,且不适合大对象或长期存活者。
老生代:给“长寿对象”的稳定居所
老生代空间大(可达几百 MB 甚至 GB),存的是全局变量、闭包捕获的外层变量、DOM 引用、长期缓存等。这些对象一旦进来,大概率会待很久。Scavenge 复制整块内存太慢、太费资源,所以改用 Mark-Sweep(标记-清除)为主:
- 从根对象(如 window、调用栈)出发,遍历并标记所有可达对象
- 扫一遍老生代,回收所有未被标记的内存
- 清除后可能留下碎片,这时再按需启动 Mark-Compact(标记-整理),把存活对象往一端挤,腾出大片连续空闲区
为避免长时间卡顿,现代 V8 还加入增量标记(把标记拆成小步,穿插在 JS 执行中)和并发标记(GC 标记与 JS 线程并行),大幅降低停顿时间。
两者怎么协作?靠“晋升”机制无缝衔接
新生代不是孤岛,它和老生代之间有明确的流动规则,这就是协作的关键:
- 一个对象在新生代经历两次 Scavenge 回收后仍存活,就被判定为“可能长期存在”,直接晋升到老生代
- 如果某次复制时 To 空间已占用超过 25%,为避免下次分配失败,也会提前把部分对象晋升过去
- 大对象(如大数组、长字符串)一创建就跳过新生代,直接分配到老生代(避免反复复制浪费)
这种设计让高频小回收(新生代)和低频大回收(老生代)各司其职,既保证了响应速度,又控制了整体内存开销。
本质上,分代不是硬性割裂,而是一套基于行为概率的优化分工:用空间换时间,用结构换效率。










