热搜:前端 nest neovim nvim

浏览器与网络(第六篇)-垃圾回收机制

lxf2023-06-20 03:31:01

前言

对于实时性和用户体验性要求高的应用来说,熟悉浏览器的V8引擎垃圾回收机制。 我们也许可以通过写低垃圾回收的代码,来提高程序的响应性,进而 提高用户体验。

本文主要讲的是 JavaScript 的垃圾回收机制,它是由V8引擎控制的

垃圾回收机制的必要性

js解析器会给字符串、对象分配内存空间,如果内存空间一直被占用着, 不进行释放,特别是一些大容量的空间,那么内存空间很容易被消耗完。

垃圾回收机制的两种策略

目前主要是采用两种策略进行垃圾回收,它可以让V8引擎知道,在内存中的哪些对象可以被释放。

  • 1. 标记清除

标记清除的过程:垃圾收集器会把在内存中的所有变量,都做上标记。然后去除在环境中的变量标记以及去掉被环境变量引用的变量的标记,之后还存在标记的对象,就是需要被回收。

  • 2. 引用计数

引用计数的过程:垃圾收集器对变量的引用次数,进行跟踪和记录。比如,声明一个变量,这个变量被一个引用类型的值赋值,那么这个值的引用次数加1;相反,如果这个值引用的变量又取得了另一个值,那么这个值的引用次数要减一。当一个值的引用次数是0时,在下次垃圾收集器运行时候,它就会被回收释放。

垃圾回收的两种方式

内存主要是在存放在堆栈中,因此垃圾的回收主要是分为栈回收和堆回收。

  • 栈的垃圾回收

主要通过ESP指针,来清理保存在栈中的函数执行上下文。 当函数执行完毕,ESP指针下移,在下次垃圾收集器运行,该函数的执行上下文 就会被回收。

  • 堆的垃圾回收

堆中的垃圾回收方式主要是分为新生代老生代

堆中的空间被分为:新生代空间和老生代空间。

新生代

特征:

类型描述
存活时间对象的存活时间短
存储大小64位操作系统 约64MB

新生代中使用的是 Scavenge算法 进行垃圾回收。

Scavenge 算法进行垃圾回收的过程:将新生代空间对半分成相同的大小,分别是对象区域和空闲区域。在垃圾回收时,把对象区域的对象进行标记,标记完成后,把存活的对象复制到空闲区域,复制完成后进行有序的排列,也就是会做标记整理,因此不会存在内存碎片的问题。这时候原来的空闲区域就变成了对象区域,原来的对象区域就变成了空间区域了,两个角色发生了翻转。这种角色的翻转可以让这两块区域无限循环使用。

老生代

特征:

类型描述
存活时间对象存活时间长(常驻内存对象)
存储空间64位的操作系统 约1.4G

使用的是三色标记算法,对老生代空间进行垃圾回收。

  • 白色:需要被回收。
  • 黑色:不需要回收的对象,其引用已经被遍历完毕。
  • 灰色:不要回收的对象,其引用没有被遍历完毕。

三色标记算法回收过程:首先扫描root(全局变量和激活的函数)对象,将非root的对象全部标记为白色;将root对象的直接引用对象入栈;将出栈的对象标记为黑色,将出栈对象的直接引用标记为灰色入栈;最后,在栈为空的时候,还是白色的对象,就是需要被回收的

在频繁的垃圾回收过程中,会在内存中存在大量的不连续空间,也就是存在内存碎片,这会造成在分配大内存的时候,空间不足。 因此,在垃圾回收后,需要进行标记-整理

标记-整理:即将内存中存活的对象,集中在一端,将边界之外的区域清除。

内存泄漏

内存泄漏:即内存被一些意料之外的对象变量长期占用不释放,以至于最后出现内存空间不足。

常见的存在内存泄漏的情况,平时搬砖的时候,需要注意:

  • 1.对象的循环引用
  • 2.元素事件绑定
const btn = document.getElementById('btn')
btn.addEventListen('click', () => {
    console.log('这里是添加按钮元素的点击事件')
})

// 这里需要注意:虽然我们在文档上删除了该元素
// 但是,事件监听器创建了该监听函数的引用,
// 它会在内存中保存该元素,以防止被垃圾回收器回收
// 这就造成了内存的泄漏
btn.remove()

// 我们需要移除事件监听
btn.removeEventListen()

// 或者移除该元素的全部事件监听
btn.removeAllListen()
  • 3.全局对象
var globalObj = {
  data: {},
  info: { name: 'jack', age: 25 }
}

// 全局变量通常情况下,是常驻于内存中的,因为
// 你不知道 在那里地方有引用到
// 比较全局对象在任何地方都可以访问到
// 因此建议使用函数作用域来 替代 全局变量的声明

const getInfo = () => {
  var globalObj = {
    data: {},
    info: { name: 'jack', age: 26 }
  }
  
  return globalObj
}

getInfo()

处理内存泄漏的一般方法是:手动给需要的对象赋值 null

优化回收机制

V8引擎优化垃圾回收机制,主要是围绕减少主线程阻塞来做文章, 因为js是单线程。从三个方面展开:

  • 1.并行回收

    在主线程执行垃圾回收的时候,开启多个辅助线程同时进行回收任务

  • 2.增量回收

    将垃圾回收的标记任务,拆分成一个个更小的任务

  • 3.并发回收

    在js主线程执行js代码的时候,在辅助线程在后台执行垃圾回收

参考文献

浅谈V8引擎的垃圾回收机制

V8内存管理及垃圾回收

JS中V8引擎内存问题

V8引擎垃圾内存回收解析

V8垃圾回收

V8引擎详解

One-Question answer

question1. 新生代的空间很小

存在于堆中的,新生代空间小的原因,其实跟其垃圾回收方式有关。新生代将对象区域中存活的对象复制到空闲区域,如果新生代的空间很大的话,那么这个复制的过程消耗的时间会很多。同时又引出了一个问题,空间设置的小,那么 就容易被填满。因此,V8引擎的处理方式是,晋升策略:也就是经过两次垃圾回收,还存在的对象,就会晋升到老生代空间。

question2. 闭包问题

V8引擎控制的垃圾回收机制,垃圾收集器会在定期运行,释放内存。但是对于存在闭包,而导致的内存泄漏,通过解决方案是:赋值 null。

function far() {
   var info = {
     name: 'jack',
     age: 25
   }
   
   function bar () {
     console.log('name is: ', info.name)
   }
   
   return bar
}

var temp = far()
temp = null // 在这里赋值 null
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!