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 | function fn() { |
上面的代码运行结果是 a 和 b 的引用计数都为 2,a 和 b 的引用计数都无法降为 0,故而造成了这部分内存始终无法被 GC 释放,随着 fn
被多次调用,a 和 b 所占用的内存就会线性增长,造成内存泄漏。
IE7&8 中有部分对象不是 js 原生对象而是 COM 对象,COM 对象的垃圾回收机制采用的就是引用计数策略。故而在 IE7&8 中访问 COM 对象如果存在循环引用就会导致内存泄漏。
循环引用的解决方法:在变量使用完后手动破坏变量之间的互相引用关系。
1 | function 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 | destroyed() { |
闭包
原因:闭包保持它的变量一直在内存中,故而使用闭包的时候要注意内存泄漏。 解决方法:将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中,删除对 DOM 的引用。
定时器和事件监听
卸载页面之前需要清除定时器和事件监听。
1 | beforeDestroy() { |
DOM
- DOM 引用了对象作为其属性或者样式。
- 将 DOM 赋值给了一个变量,最后要给变量赋值 null。
WeakMap和WeakSet 解决内存泄漏
es6 提供了 WaekMap 和 WeakSet 来解决引用带来的内存泄漏。
1 | const wm = new WeakMap(); |
GC 会忽略 wm
对于 element
的引用,故而 element
实际的引用计数为 1。