什么是内存泄露
程序的运行需要内存。只要程序提出要求,操作系统就会给内存。进程应及时释放不再用到的内存。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)
大多数语言提供自动内存管理,减轻程序员的负担,这被称为垃圾回收机制(garbage collector)
垃圾回收
引用计数
思想:跟踪记录所有值被引用的次数
(阮一峰说引用计数是最常使用的,红宝书说到 2008 年为止标记清除是最常用的)
JS 引擎有一张”引用表”,保存了内存里面所有的资源(通常是各种值,比如数组)的引用次数。如果一个值的引用次数是 0,就表示这个值不再用到了,因此可以将这块内存释放
如果一个值不再需要了,引用数却不为 0,垃圾回收机制无法释放这块内存,从而导致内存泄漏
1 | let arr = [1, 2, 3, 4]; |
上面代码中,数组[1, 2, 3, 4]是一个值,会占用内存。变量 arr 是仅有的对这个值的引用,因此引用次数为 1。尽管后面的代码没有用到 arr,它还是会持续占用内存
可以手动解除引用:让变量 = null。则数值这个值就能被垃圾回收机制释放了
缺点
循环引用的情况,eg. 对象 A 包含一个指向对象 B 的指针,而对象 B 中也包含一个指向 A 的指针。采用引用计数策略时,函数执行后,两个对象还将继续存在,因为它们的引用次数永远不会为 0。
标记清除
思路:给当前不使用的值加上标记,然后再回收其内存
只有在环境中的变量及被环境中的变量引用的变量(闭包)标记
1 | function test() { |
常见 JavaScript 内存泄露
意外的全局变量
eg. 如下代码,这种生命方式等于 this.bar=.....
,函数独立调用的话 this 为 window,所以 bar 是全局变量
1 | function foo(arg) { |
JavaScript 文件头部加上 ‘use strict’,可以避免此类错误发生
但是其实我们自己设全局变量也是有风险的,因为全局变量被认为的不可回收的(标记清除机制无法清除,因为全局环境到最后才会销毁)。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null
被遗忘的 setInterval 或回调函数
用完 setInterval 记得 clearInterval
闭包
不解释
内存泄漏的识别方法
Chrome 浏览器
控制台-profiles
Node
process.memoryUsage(),返回一个对象,包含了 Node 进程的内存占用信息
解决方法
- 找到问题后手动释放内存
- 使用两种新的数据结构:WeakSet 和 WeakMap。它们对于值的引用都是不计入垃圾回收机制
参考资料
《Javascript 高级程序设计》