引言
我们在面试的时候经常会被问到JavaScript的垃圾回收机制,这时,我们可以按照以下逻辑去回答:
-
什么是垃圾回收机制
-
为什么要使用垃圾回收机制
-
JS中什么是垃圾
-
对垃圾回收策略进行说明(可以具体讲讲标记清除法和引用计数法)
-
列举一下常见的内存泄露问题
什么是垃圾回收机制
- 垃圾回收机制全称为Garbage Collection(GC)
- 程序在创建对象或数组等引用类型实体的时候,系统会在堆内存上为之分配一段内存区,用来保存这些对象,当这些对象永久地失去引用后,就会变成垃圾,等待系统垃圾回收机制进行回收。
为什么使用垃圾回收机制
在JavaScript中的字符串、对象、数组等数据的内存是不固定的,只有真正使用的时候才会动态分配内存。当程序不再使用一个对象时,但这个对象一直占用着内存单元,这种垃圾对象过多,就会占用大量空间,如果一直不释放就会影响系统性能,重则导致程序崩溃,所以就需要垃圾回收释放这部分内存。
js中什么是垃圾
当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序变慢。
垃圾回收策略
1.标记清除法
标记清除法分为标记和清除两个阶段,标记阶段则是给所有的活动对象做上标记,清除阶段就是给没有标记的(即非活动对象)进行销毁。
步骤
- 在运行时将所有的变量都加上一个标记,开始的时候默认所有的对象都为垃圾,都标记为0
- 从各个根对象上开始遍历,把不是垃圾的节点改为1
- 清除所有标记为0的垃圾,销毁并释放它们所占用的内存
- 最后把所有内存中对象标记修改为0,等待下一轮的垃圾回收
标记清除法优缺点
- 优点:
- 实现比较简单,只有标记和不标记两种情况,通过二进制0和1就可以为其标记
- 缺点:
- 在清除之后,剩余对象的内存位置是不变的,这导致了空闲内存空间是不连续的,这样就出现了内存碎片,并且由于内存是不同大小的,这就会影响内存分配的问题
2.引用计数法
当一个对象的引用次数变为0时,这个变量就不会被再次使用,也无法使用,这时垃圾回收器就会执行,销毁并回收其占用的内存
步骤
- 跟踪记录每个变量值被使用的次数
- 当声明一个变量并且将一个引用类型数据赋值给这个变量的时候,这个引用类型数据的引用次数就标记为1
- 如果放这个引用类型数据再次被赋值给另外一个值的时候,这个引用次数就➕1
- 如果变量被其他的值覆盖 那就➖1
- 当这个引用类型的数据的引用次数变为0的时候,这个引用变量就不再被使用,垃圾回收器就会将其销毁并回收其占用的内存
引用计数法问题:循环引用引发问题
在使用引用计数法进行销毁回收变量的时候,会出现一个最大的问题,无法回收循环引用的对象。
即在一个函数中,对象a的属性指向对象b,对象b的属性指向对象a,当这个函数执行完后,对象a和对象b的计数器不会变为0,影响了正常的垃圾回收机制
function test()
{
let A = new Object();
let B = new Object();
A.pointer= B;
B.pointer = A;
}
test();
当对象A和对象B的属性相互引用,按照引用计数策略,他们的引用计数都是为2,但是在test()执行完成后,在函数执行完,函数作用域中的数据对象A和对象B都应该被GC销毁掉。但事实上按照以上写法,是无法达到该目标的,甚至如果执行多次,将会造成严重的内存泄漏。
解决方法:在函数结束时,将其指向null
//切断引用关系
A = null;
B = null;
引用计数法优缺点
- 优点:
- 引用次数为0的时候,发现垃圾立刻回收
- 最大限度减少程序暂停
- 缺点:
- 无法回收循环引用的对象
- 空间开销较大
常见内存泄漏场景
- 循环引用
- 闭包
- 全局变量
- 没有清理的DOM元素引用
- 被遗忘的定时器以及其中的引用
若想更加详细了解内存泄漏,[可点击右边传送门](内存泄漏 - 掘金 (juejin.cn))