预标记是V8在分代回收中采用的启发式优化技术,通过统计构造函数多次晋升行为,将“大概率长期存活”的对象直接分配到老生代,避免其在新生代反复Scavenge,从而降低GC频率、提升回收效率。

预标记(Pre-tenuring)不是一种独立的垃圾回收算法,而是 V8 引擎在分代回收策略下采用的一种启发式优化技术:它通过观察对象的生命周期模式,提前将“大概率长期存活”的对象直接分配到老生代,从而避免它们在新生代中反复经历多次 Scavenge 回收。
预标记的核心逻辑:用历史行为预测未来存活时间
新生代内存小(通常 1–8MB)、回收频繁(毫秒级),但只适合真正“短命”的对象。如果某个构造函数(如 new Array()、new MyClass())创建的对象,在过去几次 GC 中几乎都活过了新生代(即被晋升到老生代),V8 就会记录这个“晋升倾向”,并在后续调用时跳过新生代,直接在老生代分配内存。
- 判断依据不是单次行为,而是多次分配-晋升的统计结果(例如连续 2–3 次晋升)
- 触发预标记的通常是构造函数或字面量模式,而非随机对象
- 该决策由 V8 的运行时反馈机制动态维护,开发者无法手动开启/关闭
它如何降低新生代 GC 频率?
新生代 GC(Scavenge)的触发条件之一是 From 空间使用率超过约 25%。若大量本该长期存活的对象(如配置对象、缓存容器、状态类实例)被错误地放入新生代,会快速填满空间,导致 Scavenge 频繁执行——即使它们每次都会被复制到 To 空间并很快晋升。
- 预标记让这些“长命对象”绕过新生代,不参与 Scavenge 流程,From/To 空间压力显著下降
- 新生代中留下的主要是真·临时对象(如循环内中间值、事件回调参数),生命周期更短、更集中,Scavenge 更高效
- 整体上减少了 Scavenge 触发次数和对象复制开销
你能感知或影响它的典型场景
你不会直接写 preTenure: true,但代码模式会影响 V8 是否启用预标记:
-
重复构造相同结构的对象:比如
function createPoint() { return { x: 0, y: 0 }; }被高频调用且返回对象常驻内存 → 后续调用可能直接分配到老生代 - 大对象(> ~16KB)自动跳过新生代:这虽非预标记,但效果类似——V8 认为大对象不太可能“短命”,直接进入老生代
-
避免反模式干扰判断:不要在循环里用
new Date()或JSON.parse()创建大量相似但无关联的对象,这种噪声会稀释 V8 的晋升统计准确性
注意:预标记不是万能的
它依赖运行时数据积累,冷启动阶段无效;对一次性函数、动态结构(如 {...obj, timestamp: Date.now()})难以建模;且仅作用于对象分配路径,不影响闭包变量或 DOM 引用等泄漏源。真正的内存优化仍需结合引用管理与对象复用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











