js中的垃圾回收机制(Garbage Collection)和内存泄漏(Memory Leak)

js中的垃圾回收机制(Garbage Collection)和内存泄漏(Memory Leak)

垃圾回收机制(Garbage Collection)

js 有自动垃圾回收机制(GC),GC 会周期性的清理不使用的变量所占用的内存空间。 不同浏览器的 GC 机制都不同,不同环境的 GC 机制也不同。

不使用的变量主要是指局部变量,GC 会在局部变量使用完后等到下一个 GC 周期时释放该变量所占用的内存空间(在堆或者栈中)。

GC 判断变量是否需要被释放是通过标记清除(更为常用)和引用计数。

手动解除内存占用,就是将变量赋值为 null: var_name = null;

垃圾回收策略

标记清除

GC 会给所有进入环境(指的是全局环境或者局部)的变量(发生在变量声明时)打上标记“进入环境”,在变量离开环境时将其标记为“离开环境“,GC 会在下一个 GC 周期时释放被标记为离开环境的变量所占用的内存。

不同浏览器标记清除的策略略有不同。

引用计数

GC 统计变量值的引用数量,引用计数变为 0 则表明该变量所占用的内存可以被释放,这样 GC 会在下一次运行的时候回收该内存空间。

故而,只声明了但是没有被使用的变量的引用计数为 1,所以也会造成内存泄漏。

引用计数会带来一个问题:循环引用。

比如:

1
2
3
4
5
6
7
8
9
10
function fn() {
var a = {};
var b = {};
a.pro = b; // a引用了b
b.pro = a; // b引用了a

// do sth
}

fn();

上面的代码运行结果是 a 和 b 的引用计数都为 2,a 和 b 的引用计数都无法降为 0,故而造成了这部分内存始终无法被 GC 释放,随着 fn 被多次调用,a 和 b 所占用的内存就会线性增长,造成内存泄漏。

IE7&8 中有部分对象不是 js 原生对象而是 COM 对象,COM 对象的垃圾回收机制采用的就是引用计数策略。故而在 IE7&8 中访问 COM 对象如果存在循环引用就会导致内存泄漏。

循环引用的解决方法:在变量使用完后手动破坏变量之间的互相引用关系。

1
2
3
4
5
6
7
8
9
10
11
12
function fn() {
var a = {};
var b = {};
a.pro = b; // a引用了b
b.pro = a; // b引用了a

// do sth

a.pro = null;
}

fn();

垃圾回收策略优化

GC 在其运行期间会造成浏览器无响应。 需要优化 GC 策略来缩短无响应 duration。

优化策略一:分代回收(Generation GC)

和 JVM GC 策略一样。

js 将待回收的变量分为临时(young generation)和持久(tenured generation),多回收 young generation,少回收 tenured generation,从而减少了整体回收的变量。

变量在 young generation 和 tenured generation 之间的迁转移需要额外的开销。

优化策略二:增量回收

增量回收的策略就是每次只回收一点,提高回收的频率。

这种方法可以使每次回收的时间很短,但是带来了回收的次数很频繁。

内存泄漏(Memory Leak)

内存泄漏:不再用到的变量占着内存并且没有被释放。

不合法的全局变量

原因 1:未声明的变量或者 this 创建的变量会引发内存泄漏。 解决方法:1. 避免申明全局变量。 2. 使用严格模式。

原因 2:vue 单页面中声明全局变量在切换页面时没有释放。 解决方法:卸载页面时销毁引用,销毁引用并不能回收内存,而是让变量脱离执行环境从而可以让 GC 在下一次执行的时候回其收内存。

vue 中的其他内存泄漏的情况:https://segmentfault.com/a/1190000012738358#item-5

1
2
3
destroyed() {
window.variate_name = null;
}

闭包

原因:闭包保持它的变量一直在内存中,故而使用闭包的时候要注意内存泄漏。 解决方法:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对 DOM 的引用。

定时器和事件监听

卸载页面之前需要清除定时器和事件监听。

1
2
3
4
beforeDestroy() {
// 清除定时器
// 清除事件监听
}

DOM

  1. DOM 引用了对象作为其属性或者样式。
  2. 将 DOM 赋值给了一个变量,最后要给变量赋值 null。

WeakMap和WeakSet 解决内存泄漏

es6 提供了 WaekMap 和 WeakSet 来解决引用带来的内存泄漏。

WeakMap
1
2
3
4
5
const wm = new WeakMap(); 
const element = document.getElementById("example");

wm.set(element, "some information");
wm.get(element); // "some information"

GC 会忽略 wm 对于 element 的引用,故而 element 实际的引用计数为 1。

ref

  1. https://www.jb51.net/article/187661.htm
  2. https://segmentfault.com/a/1190000012738358
  3. http://www.ruanyifeng.com/blog/2017/04/memory-leak.html

评论