内存泄漏和弱引用

在本文中,你将了解:

  • 怎样避免内存泄漏:解除引用WeakMap
  • 什么是强引用弱引用
  • 2种垃圾回收策略(标记清除引用计数),及其优缺点

怎样避免内存泄漏

  1. 解除引用
  2. 利用WeakMap的弱引用

解除引用

解除引用: 将不再使用的数据设置为null,从而释放其引用。先看一个例子:

1
2
3
4
5
6
7
let obj = { name: 'toto' }
// { name: 'toto' }这个对象能够被读取到,因为obj这个变量名有对它的引用

// 将引用覆盖掉
obj = null

// 这个对象将会被从内存中移除,因为我们已经失去了对它所有的引用

解除引用在强引用下失效

再来看另外一个例子:

1
2
3
4
let obj = { name: 'toto' }
let arr = [ obj ]

obj = null

在这个例子中,对象 {name:'toto'} 不会被从内存中移除,因为数组arr保存了对它的引用

强引用和弱引用的区别

事实上,javascript中的大多数变量都保存着对一个对象强引用。比如上面这个数组保存着对对象{name:'toto'}的强引用。

如果一个变量保存着对一个对象的强引用,那么这个对象将不会被垃圾回收,但是如果一个变量只保存着对这个对象的弱引用,那么这个对象将会被垃圾回收,所以可以利用WeakMap

WeakMap

先来看看mdnWeakMap的描述:

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

也就是说,WeakMapkey 只能是 Object 类型。 原始数据类型 是不能作为 key 的(比如 Symbol)。

Map和Weakmap的比较

使用map,对象会占用内存,可能不会被垃圾回收。Map对一个对象是强引用

1
2
3
4
5
6
let obj = { name: 'toto' }
let mapObj = new Map()
mapObj.set(obj, 'any value')

obj = null
mapObj.size() // 1

Weakmap则完全不同,它只保存对对象的弱引用,所以不会阻止对对象的垃圾回收

1
2
3
4
5
6
let obj = { name: 'toto' }
let weakmapObj = new WeakMap()
weakmapObj.set(obj, 'any value')

obj = null
weakmapObj .size() // 0

对象被垃圾回收器删除,因为weakmap在对象{ name: ‘toto’ }上只有弱引用,而这个对象已经没有强引用了。(只有变量obj有保持引用)

WeakMap的应用场景

  1. 想临时记录数据或关系
  2. 在vue3中大量使用了WeakMap

内存泄漏

当一个对象不再被使用,但是由于某种原因,它的内存没有被释放,这就是内存泄漏。

为什么需要垃圾回收机制

  • 内存被消耗完:JS里的字符串对象数组是没有固定大小的,解释器会动态分配内存来存储这些数据,当解释器消耗完系统中所有可用的内存时,就会造成系统崩溃。
  • 内存过大:在某些情况下,不再使用到的变量所占用内存没有及时释放,这种内存泄漏导致程序运行中,内存越占越大,极端情况下可以导致系统崩溃,服务器宕机。

垃圾回收机制

  • JavaScript有自己的一套垃圾回收机制,JavaScript的解释器可以检测到什么时候程序不再使用这个对象了(数据),就会把它所占用的内存释放掉。
  • 垃圾回收机制有以下两种方法(常用):标记清除(现代),引用计数(之前)

2种垃圾回收策略

  1. 标记清除标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
  2. 引用计数:它把对象是否还被需要,简化定义为,有没有被其它地方引用。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收。

标记清除的缺点

  1. 内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块。
  2. 分配速度慢,因为即便是使用 First-fit 策略,其操作仍是一个 O(n) 的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢。

解决以上的缺点可以使用 标记整理(Mark-Compact)算法 标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存(如下图)

引用计数的缺点

  1. 需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。
  2. 解决不了循环引用导致的无法回收问题(比如IE 6、7,JS对象和DOM对象循环引用,清除不了,导致内存泄露)

V8 的垃圾回收机制

V8 的垃圾回收机制也是基于标记清除算法,不过对其做了一些优化。

  • 针对新生区采用并行回收。
  • 针对老生区采用增量标记与惰性回收

参考:

  1. Weakmap in javascript
  2. (翻译)Weakmap详解
  3. mdn