聊聊JavaScript垃圾回收机制

300 阅读5分钟

引言

  我们在面试的时候经常会被问到JavaScript的垃圾回收机制,这时,我们可以按照以下逻辑去回答:

  1. 什么是垃圾回收机制

  2. 为什么要使用垃圾回收机制

  3. JS中什么是垃圾

  4. 对垃圾回收策略进行说明(可以具体讲讲标记清除法和引用计数法)

  5. 列举一下常见的内存泄露问题

什么是垃圾回收机制

  • 垃圾回收机制全称为Garbage Collection(GC)
  • 程序在创建对象或数组等引用类型实体的时候,系统会在堆内存上为之分配一段内存区,用来保存这些对象,当这些对象永久地失去引用后,就会变成垃圾,等待系统垃圾回收机制进行回收。

为什么使用垃圾回收机制

  在JavaScript中的字符串、对象、数组等数据的内存是不固定的,只有真正使用的时候才会动态分配内存。当程序不再使用一个对象时,但这个对象一直占用着内存单元,这种垃圾对象过多,就会占用大量空间,如果一直不释放就会影响系统性能,重则导致程序崩溃,所以就需要垃圾回收释放这部分内存。

js中什么是垃圾

  当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序变慢。

垃圾回收策略

1.标记清除法

  标记清除法分为标记和清除两个阶段,标记阶段则是给所有的活动对象做上标记,清除阶段就是给没有标记的(即非活动对象)进行销毁。

步骤

  1. 在运行时将所有的变量都加上一个标记,开始的时候默认所有的对象都为垃圾,都标记为0
  2. 从各个根对象上开始遍历,把不是垃圾的节点改为1
  3. 清除所有标记为0的垃圾,销毁并释放它们所占用的内存
  4. 最后把所有内存中对象标记修改为0,等待下一轮的垃圾回收

标记清除法优缺点

  • 优点:
    • 实现比较简单,只有标记和不标记两种情况,通过二进制0和1就可以为其标记
  • 缺点:
    • 在清除之后,剩余对象的内存位置是不变的,这导致了空闲内存空间是不连续的,这样就出现了内存碎片,并且由于内存是不同大小的,这就会影响内存分配的问题

2.引用计数法

  当一个对象的引用次数变为0时,这个变量就不会被再次使用,也无法使用,这时垃圾回收器就会执行,销毁并回收其占用的内存

步骤

  1. 跟踪记录每个变量值被使用的次数
  2. 当声明一个变量并且将一个引用类型数据赋值给这个变量的时候,这个引用类型数据的引用次数就标记为1
  3. 如果放这个引用类型数据再次被赋值给另外一个值的时候,这个引用次数就➕1
  4. 如果变量被其他的值覆盖 那就➖1
  5. 当这个引用类型的数据的引用次数变为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))