V8 怎样执行和回收 JavaScript?JIT、隐藏类、新生代与老生代分别是什么?

先理解原理和示例,再练习完整口述与追问。

源码解析为 AST,Ignition 生成并解释字节码,收集类型反馈后由 Sparkplug、Maglev、TurboFan 分层编译成机器码,假设失效会去优化。隐藏类加内联缓存加速属性访问。分代回收:新生代 Scavenge 复制,老生代标记清除与整理,并通过增量、并发与并行减少停顿。

JIT(即时编译)指运行时根据真实执行情况编译热点代码。V8 先快速生成字节码解释执行,同时记录某个位置实际见过的类型;函数变热后交给更高层编译器,TurboFan 会基于“这里总是数字”“对象总是同一形状”的假设生成高度优化的机器码。一旦假设被打破,例如突然传入字符串,就去优化回到字节码。

对象的属性布局用隐藏类(V8 内部称 Map)描述。属性按相同顺序添加的对象共享隐藏类,属性访问可以用内联缓存直接按偏移读取;同一位置见过太多形状会变成多态甚至超多态,速度下降。

垃圾回收基于“大多数对象很快死亡”的假设。新生代分两个半区,Scavenge 把存活对象复制到另一半,代价与存活对象数量相关;存活多次的对象晋升到老生代。老生代使用标记-清除,并按需整理碎片,标记工作可以增量、并发进行,清理也可延迟或并行,从而缩短主线程停顿。

在热点函数里构造对象时,统一初始化 { x, y } 的属性顺序,不要有时先加 y;避免在热路径上 delete 属性,可改为赋值 undefined;数组尽量保持同类元素,往整数数组里塞入字符串会改变元素种类。排查内存问题时,看堆快照中对象的保留路径:泄漏是“仍被引用但不再需要”,GC 无法回收可达对象。

不要背固定的堆大小上限,它随版本和系统变化,Node 可用 --max-old-space-size 调整。微优化前先用性能面板确认热点,引擎版本更新后,具体编译层级和启发式也会变化。

V8 先把代码解析成 AST,由 Ignition 生成字节码解释执行,并收集类型反馈;热点函数再交给 Sparkplug、Maglev、TurboFan 等编译器生成机器码,假设失效就去优化。属性访问靠隐藏类和内联缓存加速,所以对象形状稳定很重要。回收采用分代:新生代用 Scavenge 复制存活对象,老生代用标记清除和整理,并通过增量和并发标记减少停顿。内存泄漏本质是对象仍可达,需要看保留路径而不是指望 GC。