Jacleklm's Blog

内存泄露和垃圾回收

2019/11/26

什么是内存泄露

程序的运行需要内存。只要程序提出要求,操作系统就会给内存。进程应及时释放不再用到的内存。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)
大多数语言提供自动内存管理,减轻程序员的负担,这被称为垃圾回收机制(garbage collector)

垃圾回收

引用计数

思想:跟踪记录所有值被引用的次数
(阮一峰说引用计数是最常使用的,红宝书说到 2008 年为止标记清除是最常用的)
JS 引擎有一张”引用表”,保存了内存里面所有的资源(通常是各种,比如数组)的引用次数。如果一个值的引用次数是 0,就表示这个值不再用到了,因此可以将这块内存释放
如果一个值不再需要了,引用数却不为 0,垃圾回收机制无法释放这块内存,从而导致内存泄漏

1
2
3
4
let arr = [1, 2, 3, 4];
console.log("hello world");

arr = null;

上面代码中,数组[1, 2, 3, 4]是一个值,会占用内存。变量 arr 是仅有的对这个值的引用,因此引用次数为 1。尽管后面的代码没有用到 arr,它还是会持续占用内存
可以手动解除引用:让变量 = null。则数值这个值就能被垃圾回收机制释放了

缺点

循环引用的情况,eg. 对象 A 包含一个指向对象 B 的指针,而对象 B 中也包含一个指向 A 的指针。采用引用计数策略时,函数执行后,两个对象还将继续存在,因为它们的引用次数永远不会为 0

标记清除

思路:给当前不使用的值加上标记,然后再回收其内存
只有在环境中的变量及被环境中的变量引用的变量(闭包)标记

1
2
3
4
5
function test() {
var a = 10; //被标记"进入环境"
var b = "hello"; //被标记"进入环境"
}
test(); //执行完毕后之后,a和b又被标记"离开环境",被回收

常见 JavaScript 内存泄露

意外的全局变量

eg. 如下代码,这种生命方式等于 this.bar=.....,函数独立调用的话 this 为 window,所以 bar 是全局变量

1
2
3
function foo(arg) {
bar = "this is a hidden global variable";
}

JavaScript 文件头部加上 ‘use strict’,可以避免此类错误发生

但是其实我们自己设全局变量也是有风险的,因为全局变量被认为的不可回收的(标记清除机制无法清除,因为全局环境到最后才会销毁)。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null

被遗忘的 setInterval 或回调函数

用完 setInterval 记得 clearInterval

闭包

不解释

内存泄漏的识别方法

Chrome 浏览器

控制台-profiles

Node

process.memoryUsage(),返回一个对象,包含了 Node 进程的内存占用信息

解决方法

  1. 找到问题后手动释放内存
  2. 使用两种新的数据结构:WeakSet 和 WeakMap。它们对于值的引用都是不计入垃圾回收机制

参考资料

《Javascript 高级程序设计》

阮一峰-JavaScript 内存泄漏教程

CATALOG
  1. 1. 什么是内存泄露
  2. 2. 垃圾回收
    1. 2.1. 引用计数
      1. 2.1.1. 缺点
    2. 2.2. 标记清除
  3. 3. 常见 JavaScript 内存泄露
    1. 3.1. 意外的全局变量
    2. 3.2. 被遗忘的 setInterval 或回调函数
    3. 3.3. 闭包
  4. 4. 内存泄漏的识别方法
    1. 4.1. Chrome 浏览器
    2. 4.2. Node
  5. 5. 解决方法