JavaScript垃圾回收原理与V8分代回收示例详解

 更新时间:2026年06月30日 09:26:32   作者:Ziky学习记录  
这篇文章主要介绍了JavaScript垃圾回收原理与V8分代回收的相关资料,在V8的老一代的垃圾回收机制中,采用的就是这种做法,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

在我们写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将会被回收。

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
  ↓
新对象 → 新生代(复制算法)
  ↓(存活多次)
晋升
  ↓
老生代(标记清楚 + 标记整理)

以上是整个分代回收基本流程,如果有兴趣可以再看看 写屏障等改进方法

内存泄漏是什么

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

具体有

意外的全局变量

functiontest() {
	a=10// 忘记 var / let
}

定时器未清除

setInterval(() => {},1000)

闭包持有大对象

function bibao() {
	let data = ...
	return function() {
		console.log(data)
	}
}
const test = bibao()
// 当 test不再被需要,仍然保持着对data引用,导致内存泄漏

如何减少或避免内存泄漏

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

比如

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

总结

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

此外,如果有兴趣可以再了解 写屏障和记忆集解决跨代引用问题。

到此这篇关于JavaScript垃圾回收原理与V8分代回收的文章就介绍到这了,更多相关JS垃圾回收与V8分代回收内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Javascript ParentNode和ChildNode接口原理解析

    Javascript ParentNode和ChildNode接口原理解析

    这篇文章主要介绍了Javascript ParentNode和ChildNode接口原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • JavaScript 中.call()的使用小结

    JavaScript 中.call()的使用小结

    .call()是JavaScript中用于显式设置函数执行上下文并立即调用该函数的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-11-11
  • js实现canvas图片与img图片的相互转换的示例

    js实现canvas图片与img图片的相互转换的示例

    本篇文章主要介绍了js实现canvas图片与img图片的相互转换的示例,具有一定的参考价值,有兴趣的可以了解一下
    2017-08-08
  • JS实现的颜色实时渐变效果完整实例

    JS实现的颜色实时渐变效果完整实例

    这篇文章主要介绍了JS实现的颜色实时渐变效果,结合实例形式分析了JavaScript结合时间函数定时触发动态改变页面元素属性的相关技巧,需要的朋友可以参考下
    2016-03-03
  • 详解V8是如何执行一段JavaScript代码原理

    详解V8是如何执行一段JavaScript代码原理

    这篇文章主要为大家介绍了详解V8是如何执行一段JavaScript代码原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • bootstrap table插件的分页与checkbox使用详解

    bootstrap table插件的分页与checkbox使用详解

    这篇文章主要为大家详细介绍了bootstrap table插件的分页与checkbox使用详解,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • JS面试题大坑之隐式类型转换实例代码

    JS面试题大坑之隐式类型转换实例代码

    这篇文章主要介绍了JS面试题大坑之隐式类型转换实例代码,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-10-10
  • js中null与空字符串

    js中null与空字符串""的区别讲解

    今天小编就为大家分享一篇关于js中null与空字符串""的区别讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • JS实现根据指定值删除数组中的元素操作示例

    JS实现根据指定值删除数组中的元素操作示例

    这篇文章主要介绍了JS实现根据指定值删除数组中的元素操作,结合实例形式总结分析了JavaScript针对数组元素删除操作的相关实现技巧,需要的朋友可以参考下
    2018-08-08
  • 如何使用pace.js美化你的网站加载进度条详解

    如何使用pace.js美化你的网站加载进度条详解

    Pace.js是一个非常有意思的js插件,可以自动的监听页面的加载数据,并且能够定制加载条,下面这篇文章主要给大家介绍了关于使用pace.js如何美化你的网站加载进度条的相关资料,需要的朋友可以参考下
    2022-02-02

最新评论