这是我参与更文挑战的第24天,活动详情查看: 更文挑战
前言
最近看到一个面试题,题目是:
你能介绍一下js的垃圾回收机制吗?
之前也看过一个垃圾回收的内容,但是很容易就忘了,今天借着这次机会,好好复习下,争取如果被问到,能够回答出来。
垃圾回收
什么是垃圾回收? (简称GC: Garbage Collection)
我们在写代码时声明变量,js引擎需要内存来存储这些变量。对于我们不再使用的变量,js引擎会定期找到这些变量,然后清除,释放内存,这个过程就叫垃圾回收。这个过程时是js引擎自动进行,我们无需手动回收处理。
举个例子说明下:
let name = '答案cp3'
name = ''
复制代码
首先先声明name
等于答案cp3
,这样在栈里面开启一个内存,存储name
,值是答案cp3
然后下一行把name给赋值''
,这样后name等于''
,答案cp3
就没有变量使用了,js引擎就会把它当作垃圾回收了
再来看看
let obj1 = {name: '答案cp3'}
let obj2 = obj1
obj1 = null
复制代码
请问这个会垃圾回收吗?
答案是不会。 我们在栈内存存了一个变量obj1
,指向堆的值{name: '答案cp3'}
, 这时候把obj1
赋值给obj2
,相当于又在栈内存存了一个变量obj2
,指向的是同一个堆; 然后这时候把obj1
赋值为null
,中断了obj1
与堆的联系,但是此时obj2
还指向堆的值,所以不会垃圾回收。
那js引擎怎么知道哪些变量是不再使用了?
主要是有2种方法:标记清除法
和引用计数法
。
标记清理法
这个是js引擎比较常用的。先标记,再清除。
流程是:
- 通过root节点开始,层层遍历,给遍历到的对象都打上标记(证明还在引用);
- 再次遍历,发现没有标记的对象,认为它是垃圾,然后把它回收。
引用计数法
js引擎记录了对象的引用次数,如果引用次数为0,则代表没有引用了,可以垃圾回收了。
var obj = {name: '答案cp3'} // {name: '答案cp3'} 引用次数加一
var obj1 = obj // 引用次数加一
var obj2 = obj // 引用次数加一,此时为3
var obj = obj1 = obj2 = null {name: '答案cp3'} 引用次数为0, 等待垃圾回收
复制代码
引用计数法不用对所有对象遍历打标记,只需要看引用次数为0就可以清除了。效率高,但是如果引用的次数很多的话,可能会占据很大空间,还有一个问题,无法清除循环引用。因为循环引用的引用次数都不为0。
var obj = {}
var obj1 = {}
obj.a = obj1
obj1.a = obj
复制代码
可以看到,obj和obj1相互引用,引用次数不为0,无法清除。
优化方法
垃圾回收正常是会阻塞js代码的执行,但是一般是无感的,但是如果回收的对象过大可能会造成页面卡顿,所以会有一些优化方法,尽量让这个过程更顺滑;
垃圾回收优化的方法:
一般有增量回收
,分代回收
, 闲时回收
。
- 增量回收:js引擎会把回收的过程分成各个小部分,每次只执行一部分。
- 分代回收:js引擎把数据分成新对象和老对象,新对象包括函数内部的变量,块作用域变量等,老对象包括全局变量,
DOM
等,然后把堆分成新生代和老生代两块区域,新对象放到新生代中,老对象放到老生代中,采用不同的算法来回收。 - 闲时回收:垃圾回收只在cpu空闲时工作,以减少阻塞。
内存泄漏
正常是不再使用的内存,应该会被回收的,但是由于某种原因无法被释放,导致一直占据着内存,这就叫内存泄漏。
会触发内存泄漏的行为:
没声明的全局变量
function test () {
name = '答案cp3'
}
test()
复制代码
正常函数调用后变量会很快被回收,但是此处没有加声明,导致这个变量没有被回收。
事件监听未移除
比如我们在当前页面对window监听事件,这时候页面跳转了或者移除了,如果window事件没有移除,那就可能还在监听,但是新页面是不需要的,导致一直占据着内存。
定时器
比如我们要每隔一秒更新页面的倒计时,如果倒计时到了,我们把页面的DOM
清除了,但是定时器没有清除,就会导致定时器一直在运行,无法回收。
闭包
function test () {
var obj = {name: '答案cp3'}
return obj
}
info = test()
复制代码
如果info
在使用后没有清除或者info
一直没有使用,导致obj
一直未被释放。
WeakSet,WeakMap对垃圾回收的影响
es6新增了WeakSet
和WeakMap
两种数据结构,WeakSet
成员必须是对象,WeakMap
键名必须是对象,然后他们都是弱引用的,就是如果对象一旦没有被其它地方使用,那么对象就会自动消失,无需进行垃圾回收。
主要是为了解决对象没有被使用了,如果忘了清除,导致一直占据着内存导致内存泄漏的问题。
var map = new Map ()
var obj = {}
map.set(obj, true)
var wp = new WeakMap ()
var obj = {}
wp.set(obj, true)
复制代码
使用Map
数据结构,如果obj
没有其它地方使用了,则还会占据着内存,Map
还会访问到
使用WeakMap
数据结构,如果obj
没有其它地方使用了,因为是弱引用,则键名obj
会自动消失,WeakMap
无法访问到了
总结
以上就是我总结的js的垃圾回收机制,简单来聊聊,算是复习和巩固了一下,希望能对你们有帮助~