[深入22] js和v8垃圾回收机制

导航

[react] Hooks

[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI

[数据结构和算法01] 二分查找和排序

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制

(一) 前置知识

(1) 一些单词

garbage: 垃圾

(2) 基本数据类型 和 引用数据类型

  • 基本数据类型
    • number string boolean null undefined symbol 一共6种
    • ( 基本数据类型 ) 的值,在内存中占据 ( 固定大小 ) 的空间,因此保存在 ( 栈内存 ) 中
  • 引用数据类型
    • object - plainObject array function regexp error date 等等
    • ( 引用数据类型 ) 的值是对象,( 栈中保存变量名,和指向堆地址的指针 ),真正的数据保存在 ( 堆内存 ) 中

(3) js中可能存在的内存泄漏

  • 全局变量
  • 未移除的事件监听
  • 未清除的定时器
  • 闭包
  • console.error

(4) 一些名词

  • GC
    • 是 ( Garbage Collection ) 的缩写,表示垃圾回收
  • 内存
    • 由可读写单元组成的可操作空间
  • 垃圾
    • 创建对象时,js会自动分配对应的 ( 内存空间 )
    • 当对象不再被 ( 引用 ) 或者 存在的对象不能从 ( 根上访问到 ) 时就是垃圾
    • 当垃圾存在时,js会自动对这些垃圾进行 ( 空间的释放和回收 )
    • 在javascript中,( 根 ) 相当于 ( 全局变量 )
  • 可达对象
    • 从 ( 根 ) 出发,可以访问到的对象,叫做可达对象

(5) 常见的GC算法 ( GC会阻塞js运行 )

  • 标记清除
  • 引用计数
  • 标记整理
  • 分代回收

(二) 垃圾回收机制

(1)被认为是垃圾的标准是什么?

(1) 程序中不再需要使用到对象 - 会被认为是垃圾

// var name = 'woow_wu7'
function func() {
  name = 'woow_wu8'
  console.log(name)
}
func()

说明:
(1) 当 func() 执行完后,如果没有任何其他地方在用到name的情况下,会被认为是垃圾
(2) 注意,如果name是全局变量的话,是不会被垃圾回收器回收的,需要手动回收,比如 name=null

(2) 程序中不能访问到的对象 - 会被认为是垃圾

func() {
 var name = 'woow_wu7'
 console.log(name)
}
func()

说明:
(1) 当func()执行完,函数外部无法访问到函数内部的变量,则会被认为是垃圾
(2) 即从根对象向下遍历,不是可达对象

(2) 标记清除法 - ( 标记,清除 )

(1) 概念

  • 标记清除法假定存在一个 ( 根对象 ),相当于javascript中的全局对象,垃圾回收器将 ( 定期 ) 从根对象开始查找,凡是从根部对象出发 ( 能扫描到的都会保留 ),( 扫描不到的将被回收 )
  • 目前基本所有的现代浏览器都采用标记清除法

(2) 具体流程

  • 大体上包括 ( 标记 ) 和 ( 清除 ) 两个主要阶段
  • 1.( 递归遍历 ) 每一个根,直到所有 ( 可达对象 ) 都被访问到,并 ( 标记 ) 所有被访问到的对象,以免将来再次遍历到同一个对象
  • 2.( 递归遍历 ) 每一个根,( 清除 ) 没有标记到的对象,并 ( 抹掉第一个阶段的所有标记 )
  • 3.( 回收对应的空间 ),将回收的空间加到 ( 空闲链表 ) 中,方便后面的程序申请空间时使用
  • 4.定期重复以上步骤
image

(3) 优缺点

  • 优点
    • 解决 ( 循环引用 ) 的问题
  • 缺点
    • ( 不会立即回收 ) 垃圾对象,即使发现了垃圾对象,也必须遍历完后才进行清除,清除时程序停止工作产生卡顿
    • 产生 ( 空间碎片化 ),即 ( 链表地址不连续 )

(3) 引用计数法

(1) 概念

  • 设置引用数,判断当前引用数是否为0,如果为0则是垃圾对象,则会被垃圾回收器回收
  • 通过引用计数器,当引用关系发生改变时,会修改引用计数器的数字

(2) 具体流程

  • 设置引用计数器
  • 当对象的引用关系发生变化时,引用计数器就会修改引用数字
  • 当引用数字为 0 时,立即回收

(3) 优缺点

  • 优点
    • 发现垃圾时,( 立即回收 )
    • 最大限度减少程序暂停,当发现内存即将到达临界点时,就开始进行引用计数清除,即空间不会被占满
  • 缺点
    • 无法回收循环引用的对象
    • 时间复杂度比较高
    • 资源消耗比较大,会对所有对象进行数值的监控和修改,本身就会占用时间和资源
  • 例子1
const obj1 = {num: 10}
const obj2 = {num: 11}
const objList = {obj1.num, obj2.num} // obj1 和 obj2 的引用次数都是1,只被objList引用

function fn() {
  const num1 = 1 // 引用次数是0,没有任何地方使用到num1,且函数外无法访问num1变量
  const num2 = 2 // 引用次数是0
  num3 = 3 // 引用次数是1,因为num3是全局变量,通过全局对象可以访问到,即globalThis(包括window和global),则不会被回收
}
fn()
  • 例子2
function fn() {
    const obj1 = { }
    const obj2 = { }
    obj1.name = obj2
    obj2.name = obj1
    return 'circular reference' // 循环引用
}
fn()

说明:obj1和obj2存在相互引用,计数器计数则不为0,当fn执行完后,垃圾回收器依然不会回收这两个对象

(3) 标记整理法 - ( 标记,整理,清除 )

(1) 概念

  • 在 ( 标记 ) 和 ( 清除 ) 中间,还添加了 ( 内存空间的整理 )

(2) 具体流程

  • 标记阶段:递归遍历,对可达对象 ( 可达对象即活动对象 ) 做标记
  • 整理阶段:清除前先进行整理,移动对象位置,让 ( 地址产生连续 )
  • 清除阶段:递归遍历,清除未标记的对象 ( 未标记的对象即非活动对象 )

(3) 优缺点

  • 优点
    • 比 ( 标记清除法 ),过程 ( 标记 -> 整理 -> 清除 ) 多了对 ( 碎片化空间 ) 的整理,即会 ( 较少碎片空间 )
    • ( 标记 -> 整理碎片化空间 -> 清除 )
  • 缺点
    • 和 ( 标记清除法 ) 法一样,( 不会立即清除垃圾对象 ),清除时程序仍然是暂停的

(三) V8中的垃圾回收策略 - 分代回收

image

新生代 和 老生代 和 大对象

  • 堆分类的话:一共分为 新生代 老生代 大对象 三类
  • 新生代:存活时间比较短的对象,生命周期短,空间利用率低即用空间换时间
    • 函数
  • 老生代:存活时间比较长的对象,生命周期长
    • 全局对象
    • 闭包
    • new命令生成实例对象
    • 新生代晋升为老生代的对象
    • JIT后代代码
  • 大对象
    • 整块分配,一次性回收

新生代储存区 和 老生代存储区

  • 内存上则分为两个储存区域:( 新生代存储区 ) 和 ( 老生代存储区 )
  • 新生代的存储区域较小
    • ( 新生代存储区 ) 又可以分为两个空间:( From使用空间 ) 和 ( To空闲空间 )
  • 老生代的存储区域较大


    image

(1) 新生代

(1) 相关概念

  • 新生代对象的回收主要采用的算法:主要采用 ( 赋值算法 ) + ( 标记整理算法 )
  • 新生代内存区继续分为:( 使用空间From ) 和 ( 空闲空间To ) 是两个 ( 等大的空间 )
  • 新生代采用 scavenge 算法 // scanvenge 是觅食,拾荒的意思

(2) 回收过程

  • 1.首先会将所有 ( 活动对象 ) 存储在 ( From空间 ),这个过程 To空间则是空闲状态
  • 2.当From空间使用到一定程度后就会出发GC垃圾回收操作,这个时候就会进行 ( 标记整理 ) 对活动对象标记并移动位置使 ( 空间连续 ),便于后续操作不会产生 ( 碎片化空间 )
  • 3.将 ( 活动对象 ) 拷贝到 ( To空间 ),拷贝完成之后活动空间就有了备份,这个时候就可以考虑回收操作了
  • 4.把From空间完成释放,回收完成 ( 即将From整理过后的连续空间的内容复制到To空间后,清除From空间 )
  • 5.对From和To名称进行调换,置换后 ( From就变成了To空间,To空间就变成了From空间 ) 继续重复之前的操作

总结:

From -> 标记整理 -> To -> 清除From -> 名称互换

(3) 新生代晋升

  • 即将新生代对象移动到老生代空间
  • 触发条件
    • 1.一轮GC后,还存活的新生代需要晋升
    • 2.在拷贝过程中,To空间的使用了超过25%,将这次的活动对象都移动至老生代空间
  • 为什么是限制To的使用率
    • 将来回收操作时要把From空间的内容 拷贝 到To空间进行交换,如果To的使用率太高,变成From之后的新的对象就存不进去了

(2) 老生代

相关概念

  • 老生代回收主要采用的算法:标记清除标记整理增量标记
    • 标记清除:虽然使用标记清除会有空间碎片化的问题,但是标记清除提升的速度是很快的
    • 标记整理:在晋升的时候且老生代区域的空间也不够容纳的时候,就会采用标记整理进行 空间优化
    • 增量标记
      • 将一整段的垃圾回收操作标记拆分成多个小段完成回收,主要是为了实现程序和垃圾回收的交替完成,这样进行 效率优化 带来的时间消耗更加的合理
      • 因为垃圾回收是会阻塞程序运行,因此垃圾回收和程序运行交替进行,没有一次性进行标记清除,而是在程序运行空隙进行增量标记,然后在标记完成后在进行垃圾回收,目的是为了尽可能小的去打断程序运行,增加效率
      • 之所有会用 ( 增量标记 ) 是因为做标记清除和标记整理时卡顿现象十分明显,需要利用更新颗粒的时间 ( 交替执行js和GC )
image
  • 图片来自网络 https://juejin.cn/post/6905397491253018631

(3) 新生代和老生代的对比

  • 新生代
    • 存活时间短,存储空间小,空间利用率低(空间换时间)
  • 老生代
    • 存活时间长,存储空间大,空间利用率高
    • 垃圾回收不适合做复制算法

资料

  • 顽皮的雪狐十七( JS内存管理及垃圾回收 ) https://juejin.cn/post/6905344706919481357
  • 顽皮的雪狐十七( v8分代回收 ) https://juejin.cn/post/6905397491253018631
  • js垃圾回收机制2(引用计数,标记清除,标记整理) https://juejin.cn/post/6887021259482365960
  • js垃圾回收机制2(引用计数,标记清除,标记整理) https://juejin.cn/post/6882714982158041096
  • 聊聊V8引擎的垃圾回收 https://juejin.cn/post/6844903591510016007#heading-0

你可能感兴趣的:([深入22] js和v8垃圾回收机制)