跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
JavaScript大前端算法

JavaScript 垃圾回收原理与 V8 分代回收

JavaScript 自动管理内存,但理解垃圾回收(GC)机制对性能优化至关重要。V8 引擎采用分代回收策略,将对象分为新生代和老生代。新生代使用复制算法快速回收短生命周期对象,老生代使用标记清除和标记整理处理长生命周期对象。常见内存泄漏原因包括意外全局变量、未清除定时器及闭包引用。避免泄漏需及时清理引用、减少全局变量并合理使用闭包。

黑客发布于 2026/3/23更新于 2026/9/23.5K 浏览

在我们编写 JavaScript 代码时,很少手动释放内存。不像 C/C++ 需要使用 free(),JS 引擎会帮我们自动回收内存。

但是,自动回收不等于不需要理解内存。很多前端的性能问题、页面卡顿、内存暴涨,其实都和垃圾回收机制有关。因此理解垃圾回收机制也尤为重要。

什么是垃圾回收?

垃圾回收(Garbage Collection,简称 GC):

自动识别'无法再访问'的对象,并释放其占用的内存。

核心问题只有一个:如何判断一个对象'没用了'?

那么基于这个定义,没有被引用的对象,就是垃圾。比如:

let obj = { name: "Tom" }
obj = null

当 obj = null 后:

  • 原来的对象不再被引用
  • 成为垃圾
  • 等待回收

内存模型基础

JavaScript 的内存中主要可以分为两个部分:

  • 栈(Stack)——存放基本类型和引用地址
    • 基本类型(number、string、boolean、null、undefined、symbol、bigint)
    • 函数的引用地址
  • 堆(Heap)——存放对象
    • 对象
    • 数组
    • 函数
    • 闭包
    • DOM 引用

例如:

let obj = { name: "Tom" }
  • obj 存在栈中,因为这个是引用地址,{ name: "Tom" } 存在堆中。

垃圾回收核心原理

那么怎么知道对象是否是垃圾?

标记清除算法

JS 引擎中有标记清除算法用来判断是否是垃圾,整个过程就是从 root 出发,看看是否能够被访问到。

Root 包括:

  • 全局对象(浏览器中的 window,Node 中的 global)
  • 当前调用栈中的变量
  • 正在执行函数里的局部变量
  • 闭包中被引用的变量

那么整个算法过程就分为标记阶段与清除阶段。

  1. 标记阶段

假设我们目前有以下这些对象:

Root ├── A
│     └── B
└── C
      D
  • A 被 Root 引用
  • B 被 A 引用
  • C 被 Root 引用
  • D 没有人引用

整个过程就是先从 root 出发,先找到了 A 与 C,为 A 与 C 打上了'可达'标记。

继续向下遍历通过 A 找到了 B,给 B 打上了可达标记,整个过程就是一次深度优先搜索(DFS)或广度优先搜索(BFS)。

遍历结束后:

  • A、B、C 被标记为'可达'
  • D 没被访问到
  1. 清除阶段

引擎会扫描整个堆内存。

对于每一个对象:

  • 如果有'标记' → 保留
  • 如果没有标记 → 释放内存

因此 D 将会被回收。

我们知道 JS 是单线程的,那么在垃圾回收的时候,主线程暂停,会执行这个垃圾回收,那么如果频繁进入垃圾回收就会造成页面卡顿。

V8 的分代回收机制

在早期垃圾回收模型中,所有对象都被一视同仁地扫描和回收。但在实际运行中,V8 发现了一个非常重要的现象:

绝大多数对象的生命周期都非常短。

例如:

function render() {
  let temp = { x: 1, y: 2 }
  return temp.x
}

在这个函数中,temp 只在函数执行期间存在。函数执行结束后,它就不再被引用,很快就会成为垃圾对象。

但与此同时,也存在一些对象会贯穿整个应用生命周期:

const config = { ... }

这些对象几乎不会被销毁。

如果所有对象都使用同一种回收策略:

  • 要么频繁扫描整个堆(性能浪费)
  • 要么回收不及时(内存膨胀)

因此,V8 采用了 分代垃圾回收机制(Generational GC)。

思想介绍

分代回收机制,将内存分成了两类:

  • 新生代(Young Generation)
  • 老生代(Old Generation)

核心思想就是新创建的对象大概率会很快死亡;存活较久的对象,大概率会继续存活。

新生代

首先,新创建的对象都先放在新生代,这个空间会比较小,大小通常比较小就几 MB,并且这个空间回收频率快,回收速度高。

整个新生代又分成两个空间:

  • From Space
  • To Space

具体新生代使用的是 复制算法(Copying Collection),也称为 Scavenge 算法。

算法流程:

From Space: [A][B][C][D]
To Space: [空]

其中:

  • A、C 还被引用
  • B、D 已经不可达

当垃圾回收执行时:

1️⃣ 停止 JS 执行

2️⃣ 从 Root 出发标记存活对象(这里与标记清除有区别,这里是标记复制,但是标记的流程是一样的)

3️⃣ 把'活着的对象'复制到 To Space

变成如下形式:

To Space: [A][C]

4️⃣ 清空整个 From Space

5️⃣ 交换两个空间角色

然后等待下一次执行。

老生代

那么什么时候对象会进入老生代呢?

进入老生代的条件是:

  • 在新生代经历多次垃圾回收仍然存活
  • 对象太大

老生代的特点就是:

  • 存放生命周期较长的对象
  • 空间更大
  • 回收频率更低
  • 回收成本更高

我们已经知道新生代的算法是复制算法,但是老生代使用的算法是:

  • Mark-Sweep(标记清除)
  • Mark-Compact(标记整理)

标记清除我们已经介绍过了,那么这里我们介绍标记整理。

在清除后,整个内存空间会存在很多空隙碎片,那么就需要进行整理:

[A][ ][B][ ][ ][C] |转换成下面的形式 [A][B][C][ ][ ][ ]

对象会被'挪动'到连续空间。

整个过程

我们可以看下面这个流程:

Root ↓ 新对象 → 新生代(复制算法) ↓(存活多次)晋升 ↓ 老生代(标记清除 + 标记整理)

以上是整个分代回收基本流程。

内存泄漏是什么

内存泄漏就是该回收的对象一直被引用,导致无法回收。

具体有:

1️⃣ 意外的全局变量
function test() {
  a = 10 // 忘记 var / let
}
2️⃣ 定时器未清除
setInterval(() => {}, 1000)
3️⃣ 闭包持有大对象
function bibao() {
  let data = ...
  return function() {
    console.log(data)
  }
}
const test = bibao()
// 当 test 不再被需要,仍然保持着对 data 引用,导致内存泄漏

如何减少或避免内存泄漏

为了避免内存问题,我们就需要针对上面可能出现的问题进行针对性解决。

比如:

  • 少创建全局变量
  • 用完及时清除引用
  • 清除定时器
  • 避免不必要的闭包
  • 避免缓存巨大对象

总结

JavaScript 在 V8 中采用基于可达性的分代垃圾回收机制,通过复制算法优化短生命周期对象,通过标记清除与整理管理长生命周期对象。

此外,还可以了解写屏障和记忆集解决跨代引用问题。

目录

  1. 什么是垃圾回收?
  2. 内存模型基础
  3. 垃圾回收核心原理
  4. 标记清除算法
  5. V8 的分代回收机制
  6. 思想介绍
  7. 新生代
  8. 老生代
  9. 整个过程
  10. 内存泄漏是什么
  11. 1️⃣ 意外的全局变量
  12. 2️⃣ 定时器未清除
  13. 3️⃣ 闭包持有大对象
  14. 如何减少或避免内存泄漏
  15. 总结

更多推荐文章

查看全部
  • Mac 系统下 Python 环境安装与配置指南(Anaconda)
  • Java 全栈开发工程师实战面试:从基础到微服务
  • Nanbeige 4.1-3B Streamlit WebUI 效果实录:表格数据生成与展示
  • Java 前缀和算法题目练习
  • 论文解读:利用人类反馈训练语言模型遵循指令
  • 数据结构:树、二叉树、堆排序与 TOP-K 问题解析
  • OpenCode 与 Claude Code 对比:开源 AI 编程工具选择指南
  • Ubuntu 22.04 下基于 ROS2 Humble 的 PX4 无人机仿真环境搭建
  • Java 类的实例化与封装详解
  • 动态规划:子数组与子串问题实战
  • 基于 WebTop 与 Tailscale 的云端 OpenClaw 真实浏览器部署方案
  • Python 股票数据接口教程:使用 mootdx 获取行情与财务数据
  • 使用 Visual Studio 2026 简化 Git 工作流程
  • ACM 竞赛算法核心进阶指南
  • AgentScope Java 实战:构建 LLM 智能体与多智能体协作
  • Claude 接入 MiniMax AI 大模型配置指南
  • Java 静态关键字、代码块与内部类详解
  • GitHub Copilot Pro 学生免费权益获取与 VS Code 配置指南
  • 基于机器学习的生态组合塘强化城市污水处理厂脱氮优化
  • GitHub Copilot SDK 与云原生多智能体系统实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online