V8引擎中变量与数据类型严格按内存区域组织:基本类型存栈,引用类型驻堆;执行上下文驱动运行,闭包变量被迁移至堆;GC基于可达性标记清除不可达对象。

JavaScript中变量、数据类型和执行上下文在V8引擎中不是随意存放的,而是严格按内存区域、生命周期和访问关系组织的。核心逻辑是:基本类型走栈,引用类型驻堆;执行上下文驱动运行,变量对象承载数据;垃圾回收器只回收不可达内容。
变量与数据类型的存储位置
V8将内存划分为栈(Stack)和堆(Heap)两块,划分依据是值的大小、生命周期和访问频率:
- 栈内存:存放原始类型(Number, String, Boolean, undefined, null, Symbol, BigInt),也包括函数调用时的执行上下文帧(如参数、局部变量声明)。特点是空间小、分配快、自动释放——函数退出时,ESP指针下移,整帧直接销毁。
-
堆内存:存放所有引用类型(Object, Array, Function, Date, RegExp, Promise等)。栈中仅保存一个指向堆中实际数据的地址。例如:
let obj = {x: 1},obj本身在栈,{x: 1}在堆。 - 字符串的特殊性:短字符串通常直接存栈(优化为“栈内驻留”),长字符串或被多次引用的字符串会进入堆,并可能被引擎内部驻留(interned)以节省空间。
执行上下文的生成与管理
执行上下文(Execution Context)是V8执行代码的“运行容器”,每个函数调用都会创建一个新的上下文,它不直接“存储”在栈或堆里,而是由调用栈(Call Stack)结构化组织:
- 全局执行上下文在脚本启动时创建,关联globalThis(浏览器中是window,Node中是global);函数执行上下文在调用时压入调用栈顶部。
- 每个上下文包含三个关键部分:变量对象(VO/AO)(保存变量、函数声明)、作用域链(Scope Chain)(用于标识符解析)、this绑定(执行时确定)。
- 函数执行完毕后,其上下文自动出栈,VO/AO随之销毁——但若存在闭包,被内部函数引用的外部变量不会被销毁,而是被提升并持久化到堆中的closure对象里(如
foo()返回的函数仍能访问myName)。
闭包与变量的“迁移”机制
当函数内部定义的函数引用了外层变量,V8不会让这些变量随外层函数退出而消失。它会做一次“变量搬家”:
立即学习“Java免费学习笔记(深入)”;
- 预编译阶段扫描到闭包引用(如
inner用了a和c),V8会在堆中创建一个closure环境记录(Closure Record),把被引用的变量从原栈帧复制或移动过去。 - 未被引用的变量(如
const test2 = 2)仍留在原栈帧中,函数退出即释放。 - 闭包环境本身也是堆对象,受垃圾回收器管理——只有当闭包函数不再可达(如
bar = null),整个闭包环境才可能被标记清除。
GC如何判断哪些内容该回收
V8不靠变量是否“还在作用域内”来回收,而是基于可达性(Reachability):
- 根对象(如全局对象、当前调用栈中的变量、正在执行的闭包环境)出发,能通过引用链访问到的对象视为“活跃”,其余视为垃圾。
- 采用标记-清除(Mark-Sweep)为主:先遍历所有根对象并递归标记可达对象;再统一清除未被标记的堆内存。
- 新生代用Scavenge算法(From/To空间复制),老生代用标记-清除+可选压缩(Compaction),避免内存碎片影响大对象分配。


















