内存泄漏和弱引用
在本文中,你将了解:
- 怎样避免内存泄漏:
解除引用
和WeakMap
- 什么是
强引用
和弱引用
- 2种垃圾回收策略(
标记清除
和引用计数
),及其优缺点
怎样避免内存泄漏
- 解除引用
- 利用
WeakMap
的弱引用
解除引用
解除引用: 将不再使用的数据设置为null
,从而释放
其引用。先看一个例子:
1 | let obj = { name: 'toto' } |
解除引用在强引用下失效
再来看另外一个例子:
1 | let obj = { name: 'toto' } |
在这个例子中,对象 {name:'toto'}
不会被从内存中移除,因为数组arr
保存了对它的引用
强引用和弱引用的区别
事实上,javascript
中的大多数变量
都保存着对一个对象
的强引用
。比如上面这个数组保存着对对象{name:'toto'}
的强引用。
如果一个变量
保存着对一个对象的强引用
,那么这个对象将不会
被垃圾回收,但是如果一个变量
只保存着对这个对象的弱引用
,那么这个对象将会被垃圾回收,所以可以利用WeakMap
。
WeakMap
先来看看mdn
对WeakMap的描述:
WeakMap
对象是一组键/值对的集合,其中的键是弱引用
的。其键必须是对象,而值可以是任意的。
也就是说,WeakMap
的 key
只能是 Object
类型。 原始数据类型
是不能作为 key
的(比如 Symbol
)。
Map和Weakmap的比较
使用map
,对象会占用内存,可能不会被垃圾回收。Map对一个对象是强引用
1 | let obj = { name: 'toto' } |
Weakmap
则完全不同,它只保存对对象的弱引用,所以不会阻止对对象的垃圾回收
1 | let obj = { name: 'toto' } |
对象被垃圾回收器删除,因为weakmap
在对象{ name: ‘toto’ }
上只有弱引用,而这个对象已经没有强引用了。(只有变量obj
有保持引用)
WeakMap的应用场景
- 想临时记录数据或关系
- 在vue3中大量使用了WeakMap
内存泄漏
当一个对象不再被使用,但是由于某种原因,它的内存没有被释放,这就是内存泄漏。
为什么需要垃圾回收机制
内存被消耗完
:JS里的字符串
,对象
,数组
是没有固定大小的,解释器
会动态分配内存来存储这些数据,当解释器
消耗完系统中所有可用的内存时,就会造成系统崩溃。内存过大
:在某些情况下,不再使用到的变量所占用内存没有及时释放,这种内存泄漏导致程序运行中,内存越占越大,极端情况下可以导致系统崩溃,服务器宕机。
垃圾回收机制
JavaScript
有自己的一套垃圾回收机制,JavaScript
的解释器可以检测到什么时候程序不再使用这个对象了(数据),就会把它所占用的内存释放掉。- 垃圾回收机制有以下两种方法(常用):
标记清除
(现代),引用计数
(之前)
2种垃圾回收策略
标记清除
:标记阶段
即为所有活动对象
做上标记,清除阶段
则把没有标记(也就是非活动对象
)销毁。引用计数
:它把对象是否还被需要,简化定义为,有没有被其它地方引用。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收。
标记清除的缺点
内存碎片化
,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块。分配速度慢
,因为即便是使用 First-fit 策略,其操作仍是一个 O(n) 的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢。
解决以上的缺点可以使用 标记整理(Mark-Compact)算法
标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存(如下图)
引用计数的缺点
- 需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。
- 解决不了
循环引用
导致的无法回收问题(比如IE 6、7,JS对象和DOM对象循环引用,清除不了,导致内存泄露)
V8 的垃圾回收机制
V8 的垃圾回收机制也是基于标记清除算法
,不过对其做了一些优化。
- 针对新生区采用并行回收。
- 针对老生区采用增量标记与惰性回收
参考: