新生代gc在eden区满时触发,老年代gc在老年代空间不足时触发;前者频繁、快、用复制算法,后者低频、慢、用标记整理或清除算法。

新生代和老生代的 GC 触发不是靠“固定时间”或“手动判断”,而是由内存使用量是否突破各自区域的硬性阈值决定的;两者的阈值差异直接导致回收频率、耗时、算法选择完全不同。
新生代 GC 什么时候触发?看 From 空间是否填满
新生代 GC(Scavenge)只在 From space 内存被占满时立即触发,不等空闲或定时。典型阈值是:1–8 MB(V8 默认约 1.5–4 MB,具体取决于平台和启动参数)。一旦分配新对象时发现 From space 没有足够连续空间,就立刻开始复制回收。
- 对象分配始终发生在
From space,To space保持空闲 - 哪怕堆总占用才 5MB,只要
From space满了(比如 2MB),GC 就会跑——和老生代完全无关 - 大对象(如 > 1KB 的数组或字符串)会跳过新生代,直接分配到老生代,避免复制开销
- 频繁创建小对象(如循环里
const item = {id: i})极易快速填满From space,引发高频 Scavenge
老生代 GC 为什么难预测?它依赖浮动阈值与增量标记
老生代 GC(Mark-Sweep/Mark-Compact)不以“空间满”为唯一条件,而是综合以下三个动态因素:
-
old_space_usage达到当前老生代堆上限的约 70–85%(非固定值,V8 会根据增长速率动态调整) - 上一次 GC 后,老生代内存增长过快(例如 1 秒内涨了 50MB),V8 会提前触发 Minor GC + 晋升检查,甚至直接触发老生代回收
- 主线程空闲期(Idle GC):如果事件循环空转超过一定毫秒(如 1ms),且老生代使用率已超安全线,V8 可能插入一次低优先级 Mark-Sweep
-
global.gc()(仅 Node.js 开启--expose-gc时可用)可强制触发,但生产环境禁用——它会打断增量标记,造成明显卡顿
为什么新生代阈值小却快,老生代阈值大反而慢?
这不是“大小”问题,而是算法本质决定的性能曲线:
- Scavenge 是复制算法:只遍历并复制存活对象(通常
- Mark-Sweep 是图遍历:必须从根(
globalThis、栈变量、DOM 引用等)出发扫描整个老生代堆,时间复杂度 O(全部对象数 + 全部引用数);100MB 老生代里有 50 万个对象?标记阶段就可能耗时 10–50ms - 晋升阈值(默认 2 次 Scavenge)不是性能开关,而是“存活稳定性”信号:一个对象扛过两次快速回收,说明它大概率会长期存在,再留在新生代反复复制就是浪费
- 真正危险的是“伪老生代压力”:大量本该短命的对象因闭包、事件监听器、缓存未清理而意外滞留老生代,让阈值还没到就频繁触发 Full GC
最常被忽略的一点:新生代阈值虽小,但它的回收是“不可见”的——你几乎感知不到;老生代阈值虽高,但每次回收都可能打断渲染帧或请求处理,而这种打断往往发生在你没监控内存增长模式的时候。











