JS

从面试题了解浏览器的垃圾回收

Posted by monkey-yu on August 19, 2020 | 浏览量: -

1. 浏览器怎么进行垃圾回收?

回答这个问题可以从三个点来回答,什么是垃圾?如何捡垃圾?什么时候捡垃圾?

1.什么是垃圾?
  • 不再需要的数据,即为垃圾
  • 全局变量随时可能用到,不是垃圾

举例:

1
2
3
let dog = new Object()
let dog.a = new Array(1)
dog.a = new Object()

当 JavaScript 执行这段代码的时候,会先在全局作用域中添加一个dog 属性,并在堆中创建了一个空对象,将该对象的地址指向了 dog

随后又创建一个大小为 1 的数组,并将属性地址指向了 dog.a

将另外一个对象赋给了 a 属性。

此时,数组对象就成了不被使用的数据,即 不可达数据。就是需要被回收的垃圾数据。

2.如何捡垃圾?
  • 标记空间中的 可达值。从根节点出发,遍历所有对象,可以遍历到的对象是可达的,没有被遍历到的即为不可达数据。
  • 回收 不可达的值所占的内存
  • 做内存整理

    在浏览器环境中,根节点有很多。比如:全局变量window 、文档DOM 树、存放在栈上的变量

3.什么时候捡垃圾?

前端有其特殊性,垃圾回收的时候会造成页面卡顿。所以垃圾回收有以下几种收集方式:分代收集、增量收集、闲时收集。

分代收集

一句话总结分代回收就是:将堆分为新生代与老生代,多回收新生代,少回收老生代。

浏览器将数据分为临时对象和长久对象。

临时对象:

  • 大部分对象在内存中存活时间很短
  • 函数内部声明的变量、块级作用域中的变量。当函数或代码块执行结束后,其中定义的变量就被销毁
  • 这类对象很快就变得不可访问,应快点回收

长久对象:

  • 生命周期很长的对象。比如全局的window、DOM、web API等
  • 这类对象应慢点回收

V8把堆分为新生代和老生代两个区域。新生代存放临时对象,老生代存放长久对象。并且让副垃圾回收器、主垃圾回收器分别负责新生代和老生代。

主垃圾回收器的特点:

  • 对象占用空间大
  • 对象存活时间长
  • 使用标记-清除算法进行垃圾回收

副垃圾回收器特点:

  • 负责新生代的垃圾回收,通常只支持1-8M的容量
  • 分为对象区域、空闲区域
增量收集

脚本中有许多对象,引擎一次性遍历整个对象,会造成长时间暂停。所以引擎将垃圾收集分成更小的块,每次处理一部分,多次处理。解决长时间停顿的问题。

闲时收集

垃圾收集器只会在cpu空闲时尝试运行,减少可能对代码执行的影响。

2.浏览器中不同类型变量的内存何时释放?

javascript中类型:值类型、引用类型。

引用类型: 在没有引用之后,通过V8自动回收

值类型:如果处于闭包中,则等闭包没有引用才会被回收;如果非闭包情况下,等V8的新生代切换时候回收。

3.哪些情况会导致内存泄露?如何避免?

内存泄露:指用不到的数据依然占据着内存空间,不能被再次利用。

以Vue为例,通常有这些情况:

  • 监听在window/body等事件没有解绑
  • 绑在EventBus的事件没有解绑
  • Vuex的$store,watch之后没有unwatch
  • 使用第三方库创建,没有调用正确的销毁函数

解决办法:beforeDestroy中及时销毁

  • 绑定了ad dEventListener事件,要removeEventListener
  • 观察者模式$on 和 $off处理
  • 如果组件中使用了定时器,应销毁处理
  • 如果在mounted/created钩子中使用了第三方库初始化,对应销毁
  • 使用弱引用weakMap 、weakSet

4.闭包会导致内存泄漏吗?

不会。内存泄露是指你「用不到」(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。闭包里面的变量就是我们需要的变量,不能说是内存泄露。