JavaScript垃圾回收机制

article/2025/10/8 4:26:15

JavaScript垃圾回收机制

  • 1 垃圾为何要产生并回收
  • 2 垃圾回收机制
    • 2.1 标记清除法
    • 2.2 引用计数法
  • 3 V8对垃圾回收机制的优化——分代式垃圾回收机制
    • 3.1 新生代与老生代
    • 3.2 新生代的垃圾回收
    • 3.3 老生代的垃圾回收

1 垃圾为何要产生并回收

当我们写代码时创建一个基本类型、对象、函数等,都是需要占用内存的,JavaScript基本数据类型存储在栈内存中,引用数据类型存储在堆内存中,但是引用数据类型会在栈内存中存储一个实际对象的引用

比如说我们创建了一个person对象,然后将person对象重新赋值:

var person = {name: "橘猫吃不胖",age: 2
}
person = [1, 2, 3];
console.log(person); // [ 1, 2, 3 ]

那么原本堆内存给person对象开辟了一个空间来存放,栈内存中存放了该引用的地址,但是在下一步中,person对象成为了一个数组,也就是说引用地址从原来的对象变成了数组,原来的引用关系就没有了,那么这时原来的对象在堆内存中就会成为一个垃圾。
在这里插入图片描述
产生的垃圾如果很多,而且一直不清理,堆积起来,就会影响系统的性能,甚至可能造成系统崩溃。

2 垃圾回收机制

JavaScript中主要的内存管理概念是可达性。

那什么是可达性呢,比如说定义一个对象:

let person = {name: "橘猫吃不胖",age: 2
}
console.log(person.name, person.age); // 橘猫吃不胖 2

person引用了这个对象,通过person.name可以获取到“橘猫吃不胖”的值,通过person.age可以获取到2,那么这时就可以认为“橘猫吃不胖”和2是可达的。

person = null;
console.log(person.name, person.age); // TypeError: Cannot read properties of null (reading 'name')

如果将person设置为null,那么这两个值就没法获得了,它们就是不可达的,这时JavaScript垃圾回收机制就会自动从内存中将其清除。

那么JavaScript的垃圾回收就是定期找出这些不可达的对象,然后将其释放。那么找出这些不可达的对象有两种常用的策略:

  • 标记清除法
  • 引用计数法

2.1 标记清除法

标记清除法分为标记清除两个阶段,标记阶段需要从根节点遍历内存中的所有对象,并为可达的对象做上标记,清除阶段则把没有标记的对象(非可达对象)销毁。

标记清除法的优点就是实现简单

它的缺点有两个,首先是内存碎片化。这是因为清理掉垃圾之后,未被清除的对象内存位置是不变的,而被清除掉的内存穿插在未被清除的对象中,导致了内存碎片化。
在这里插入图片描述
第二个缺点是内存分配速度慢。由于空闲内存不是一整块,假设新对象需要的内存是size,那么需要对空闲内存进行一次单向遍历,找出大于等于size的内存才能为其分配。
在这里插入图片描述

标记清除算法改进—— 标记整理算法

标记清除算法的缺点主要在于内存清理之后剩余的内存位置不变而导致内存碎片化,因此可以使用标记整理算法改进。

标记整理算法的标记阶段与标记清除算法相同,都是从根节点遍历内存中的所有对象,为可达的对象打上一个标记。但是在标记结束后,标记整理算法将这些可达的对象移向内存的一端,然后清理掉边界的内存。
在这里插入图片描述

2.2 引用计数法

引用计数法主要记录对象有没有被其他对象引用,如果没有被引用,它将被垃圾回收机制回收。它的策略是跟踪记录每个变量值被使用的次数,当变量值引用次数为0时,垃圾回收机制就会把它清理掉。

示例代码如下:

let person = { name: "橘猫吃不胖" }; // { name: "橘猫吃不胖" } 引用次数为1
let person1 = person; // { name: "橘猫吃不胖" } 引用次数为2
person = null; // { name: "橘猫吃不胖" } 的引用次数为1
person1 = null; // { name: "橘猫吃不胖" } 的引用次数为0

引用计数法的优点是可以实现立即进行垃圾回收。当引用计数在引用值为0时,立即进行垃圾回收,这样可以达到立刻垃圾回收的效果。

它的缺点也有两个,首先它需要一个计数器,这个计数器可能要占据很大的位置,因为我们无法知道被引用数量的多少。

第二个缺点是无法解决当出现循环引用时无法回收的问题。例如a引用了bb也引用了a,两个对象相互引用,引用计数不为0,因此无法进行内存清理,如下所示:

let a = { name: "橘猫吃不胖" };
let b = { age: 2 };
a.age = b;
b.name = a;

3 V8对垃圾回收机制的优化——分代式垃圾回收机制

目前大多数浏览器都是基于标记清除算法,V8进行了一些优化加工处理,采用分代式垃圾回收机制。

3.1 新生代与老生代

原本的垃圾回收机制在每次回收时都要检查内存中所有的对象,这样的话,一些大、老、存活时间长的对象与新、小、存活时间短的对象检查频率相同,但是前者并不需要频繁进行清理,因此采用分代式垃圾回收机制。

V8中将堆内存分为新生代老生代两区域,采用不同的垃圾回收策略进行回收。新生代的对象为存活时间较短的对象,通常只支持1~8M的容量,老生代的对象为存活时间较长或常驻内存的对象,容量通常比较大,V8整个堆内存的大小就等于新生代加上老生代的内存。
在这里插入图片描述

3.2 新生代的垃圾回收

新生代垃圾回收策略中,将堆内存一分为二,一个是处于使用状态的使用区,一个是处于闲置状态的空闲区。
在这里插入图片描述
新加入的对象都会存放到使用区,当使用区快满时,就需要执行一次垃圾清理操作,即新生代垃圾回收机制会对使用区中的活动对象(不需要被清理的对象)做标记,标记完成之后将这些活动对象复制到空闲区并进行排序(避免内存碎片化),然后将使用区清空,原来的空闲区变为使用区,原来的使用区变为空闲区。
在这里插入图片描述
当一个对象经过多次复制后依然存活,它将会被认为是生命周期较长的对象,会被移动到老生代的内存中,或者一个对象被复制到空闲区时,空闲区占用空间超过了25%,那么该对象也会进入老生代内存中。

新生代回收策略——并行回收

JavaScript是单线程的语言,当执行垃圾回收时,就会阻塞JavaScript脚本的执行,垃圾回收结束后再继续JavaScript脚本执行,这种情况叫做全停顿(Stop-The-World)

如果执行一次垃圾回收需要100ms,那么脚本执行就得暂停100ms,如果执行垃圾回收的时间过长,那么就会造成页面卡顿,带来不好的用户体验。对于这样的情况,可以采用并行回收的策略。

并行回收指的是在主线程进行垃圾回收时,同时开启多个辅助线程一起执行垃圾回收。比如说一项任务一个人需要30天才能完成,那么如果安排两个人甚至多个人,可能10来天甚至更短的时间就完成了。实现并行回收可以大大降低垃圾回收的暂停时间。

新生代对象空间就采用并行策略,在执行垃圾回收的过程中,会启动了多个线程来负责新生代中的垃圾清理操作,这些线程同时将对象空间中的数据移动到空闲区域,这个过程中由于数据地址会发生改变,所以还需要同步更新引用这些对象的指针,此即并行回收。
在这里插入图片描述

3.3 老生代的垃圾回收

老生代的垃圾回收操作主要就是标记清除算法的步骤了,在标记阶段标记所有的可达对象,清除阶段清除掉未被标记的对象。又由于该算法会出现内存碎片的问题,因此会使用标记整理算法来优化这个过程。


老生代回收策略——增量标记与惰性清理

①增量标记

增量就是将一次标记的过程,分成了许多次,每执行完一次就让应用逻辑执行一会儿,这样交替多次后完成垃圾回收。但是这会随之而来新的问题,首先是如何暂停每次标记去执行JavaScript代码,还有如果标记好的对象在执行js中改变了状态成为了可达或者不可达对象怎么办,V8对这两个问题对应的解决方案分别是三色标记法与写屏障。
在这里插入图片描述

a.三色标记法

三色标记法使用三种颜色白、灰、黑来标记对象的状态。白色表示初始状态,黑色表示已检查状态,灰色表示待检查状态。

它的过程为:
1、将所有的对象设置为白色,然后从root对象出发,将所有可以访问的对象标记为灰色,并用一个数组缓存起来;
2、遍历该数组,每次都把要遍历的对象标记为黑色并移出,并且把他的相邻节点都涂成灰色,并放入队列,直到队列为空
3、继续检查是否有灰色对象,如果有继续放入队列然后循环,直到所有的可访问对象都变成黑色

采用三色标记法后,程序在恢复执行时可以直接判断当前内存中有没有灰色节点,如果有灰色节点,那么从灰色节点开始继续执行,如果没有,直接进入垃圾清理阶段。

b.写屏障

写屏障可以解决第二个问题,如果执行任务程序时内存中标记好的对象引用关系被修改了,比如说黑色对象引用了白色对象,那么它就会将白色对象改成灰色对象,这样就可以保证下一次标记时可以正常进行。

②惰性清理

增量标记完成后,就开始清除垃圾。如果当前的可用内存可以支持快速的执行代码,就没必要立即清理内存,而且清理时没必要一次性清理完,可以按需清理。

优点:大大减少了主线程停顿的时间,让用户与浏览器交互的过程变得更加流畅
缺点:并没有减少主线程的总暂停的时间,甚至会略微增加


老生代回收策略——并发回收

并发回收指的是主线程在执行JavaScript的过程中,辅助线程能够在后台完成执行垃圾回收的操作,辅助线程在执行垃圾回收的时候,主线程也可以自由执行
在这里插入图片描述

本文学习自掘金大佬 isboyjc 的文章:「硬核JS」你真的了解垃圾回收机制吗
多次阅读之后,我受益匪浅,因此写该文章记录一下~


http://chatgpt.dhexx.cn/article/6keGcWo3.shtml

相关文章

mysql初学——“[ERROR] [MY-012271] [InnoDB] The innodb_system data file 'ibdata1' must be writable”

这个问题一般会在安装后,想要启动mysql的时候出现。之前在网上查到过处理这类问题的解决方案,是删除一些文件,后面发现没有用,并且导致其他一些问题的出现。在stackoverflowed网站中发现一种解释是mysql已经运行了,发现…

MySQL的ibdata1文件无了该怎么恢复

前段时间电脑突然坏了,系统盘全部格式化了,只剩data源文件,装上MySQL把源文件放进去后发现所有库的表打不了开了, 我的mysql是5.7的版本,看网上说是ibdata1文件的问题,以前的ibdata1文件已经没了&#xff…

windows mysql 启动失败 :The innodb_system data file ‘ibdata1‘ must be writable

net start mysql 启动失败 在安装目录如:D:\mysql8\bin 下按住shift点击右键,启动cmd或者PowerShell 执行命令:mysqld --console 查看日志 注意error级别的记录:The innodb_system data file ‘ibdata1’ must be writable 进入…

MySQL 5.6 如何给ibdata1瘦身

前不久刚给ibdata1瘦身,发篇文章总结下。 ibdata1是MySQL使用InnoDB引擎时所产生的文件,其一般存储数据、索引、结构、缓冲数据、共享数据和重做日志等。因为ibdata1只增不减,长期操作数据库,可能会使其越来越大,而浪费…

误删mysql8下ibdata1文件恢复数据

一位小伙伴不小心误删了生产环境上mysql数据下/data/mysql/ibdata1和ib_logfile0、ib_logfile1文件,并且mysql服务停止了,造成mysql重启不了,吓得小伙伴以为要“被删库跑路”了,于是赶紧帮忙”救火”。像这种误删数据或者删库的情…

MySQL ibdata1 文件“减肥”记

夏天来了,没想到连 ibdata1 文件也要开始“减肥”了~ 作者:杨彩琳 爱可生华东交付部 DBA,主要负责 MySQL 日常问题处理及 DMP 产品支持。爱好跳舞,追剧。 本文来源:原创投稿 有句话是这么说的:“在 InnoDB…

【无标题】1.[ERROR] InnoDB: The innodb_system data file ‘ibdata1‘ must be writable

问题原因: 文件的权限导致运行MySQL服务的用户无法对这些文件进行写入导致的报错. 解决方案: 方案一: 初始化MySQL服务时记得使用"–user"指定运行mysql服务的用户. 方案二: 直接进入到数据目录使用"chown"命令修改权限即可.

【MySQL】MySQL发生系统错误、The innodb_system data file ‘ibdata1‘ must be writable问题的解决

1. 启动mysql服务时:发生系统错误5 使用命令行 net start MySql 后报错:发生系统错误 5。 说明权限不够 ,需要使用管理员身份运行cmd。 但是每次都需要搜索后然后右键选择以管理员身份运行很麻烦,这里介绍一个一劳永逸的办法&am…

MySQL的ibdata1文件占用过大瘦身

处理MySQL的ibdata1文件过大问题 本人在对数据库进行大量的数据插入和删除的时候,发现ibdata1的占了将近一个T ibdata1文件是什么? ibdata1是一个用来构建innodb系统表空间的文件,这个文件包含了innodb表的元数据、撤销记录、修改buffer和双…

【数据库篇】MySQL InnoDB ibd 文件格式解析

mysql innodb的表由.frm .ibd 组成,frm:存了每个表的元数据,包括表结构的定义等;ibd:存了每个表的元数据,包括表结构的定义等; 1.0 ibd文件基本结构 ibd文件由Tablespaces,Segments,Extents,P…

mysql 里的 ibdata1 文件

为什么 mysql 里的 ibdata1 文件不断的增长? 转自:http://linux.cn/article-5829-rss.html ibdata1 file 我们在 Percona 支持栏目经常收到关于 MySQL 的 ibdata1 文件的这个问题。 当监控服务器发送一个关于 MySQL 服务器存储的报警时,恐慌…

mysql ibdata1

ibdata1是什么? Mysql ibdata1即Innodb data1缩写,是innodb引擎的表空间,用于存放 数据字典Data dictionary: 只读的表,存储对象的相关信息,如占用空间,列的缺省值,约束信息&…

MySQL 中的 ibdata1

系统表空间是InnoDB数据字典、双写缓冲区、更改缓冲区和撤消日志的存储区域 。如果表是在系统表空间中创建的,而不是在每个表文件或通用表空间中创建,则它还可能包含表和索引数据。 系统表空间可以有一个或多个数据文件。默认情况下,ibdata1…

分享篇 | MySQL的ibdata1是个啥,为啥越来越大,怎么缩小?

同事的一个问题: MySQL的ibdata1文件越来越大,这是为啥、 看着别扭,怎么搞小它? ibdata1文件是什么? ibdata1是一个用来构建innodb系统表空间的文件,这个文件包含了innodb表的元数据、undo日志、修改buffe…

js类型转换题

考察隐式类型转换: 1.号一侧出现了字符串,就用String()将不是字符串的变成字符串,最后拼接在一起 2.-号则会调用显示类型转换Number(),将非数字转换成数字,进行计算 true 0 和 true false 都是隐式调用Number()变成…

JS中的强制类型转换

概念 这里我们首先需要知道什么是值类型转换 值类型转换:将值从一种类型转换为另一种类型,就是类型转换,分显示转换和隐式转换 js类型转换出的值都是基本类型(number、boolean、string、null、undefined、string)&a…

JavaScript——数据类型的转换

目录 一、其他类型转化成字符串类型 1. 把数字型转换为字符串型 变量.toString() 2. 利用 String(变量) 3. 利用 拼接字符串的方法实现转换效果 隐式转换 二、其他类型转化成数字类型(重点) 1. parseInt(变量) 可以把 字符型的转换为数字型…

JavaScript类型转换规则

类型转换 先梳理一下es6之前有五种基本数据类型:Null、Undefined、String、Number 、Boolean 布尔类型转换规则 直观上为空的值(0、空字符串、null、undefined、和NaN)将变为false 注意 :包含"0" 为true console.log(Boolean("")); //falseconsole.log(…

JavaScript类型转换

javascript是一种弱类型的语言,变量和类型无关,所以有时需要我们进行类型转换 一、数字型转换(number) 两种方法: 1、number:类型转化走的是v8引擎最底层机制的转化规则: 先将引用类型转化为…

js类型转换

NaNi??? 一、显性类型转换 强制类型转换主要是指通过String、Number和Boolean等构造方法手动转换成对应的字符串、数字和布尔值。 1.1 转为字符串 1.1.1 原始类型转字符串 原始类型字符串string :‘str’“str”number : 123“123”Boolean“true”/“fals…