转载地址
什么是内存泄漏?
程序的运行需要内存。只要程序需要,操作系统或操作就必须提供内存。
对于持续运行的服务进程,必须及时释放内存,否则内存使用量会越来越高,至少会影响系统性能,导致进程崩溃。
不再使用且未及时释放的内存称为内存泄漏。
有些语言(如C语言)必须手动释放内存,由程序员负责内存管理。
这很麻烦,所以大多数语言都提供了自动内存管理来减轻程序员的负担。这称为“垃圾收集机制”。
javascript垃圾回收机制的原理:
为了解决内存泄漏,垃圾回收机制会周期性地(周期性地)找出那些不再使用的内存(变量),然后释放内存。
目前主流浏览器常用的垃圾回收机制有两种:标记移除和引用计数。
标记去除:
js 中最常用的垃圾回收方式是标记去除。当一个变量进入环境时,例如在函数中声明一个变量时,该变量被标记为“进入环境”。从逻辑上讲,环境变量占用的内存是永远无法释放的,因为只要执行流程进入相应的环境,就有可能被使用。当变量离开环境时,它被标记为“离开环境”。
function test(){
var a = 10; //被标记"进入环境"
var b = "hello"; //被标记"进入环境"
}
test(); //执行完毕后之后,a和b又被标记"离开环境",被回收垃圾回收机制运行时,会对内存中存储的所有变量进行标记(可以是任何标记方法),然后将环境中的变量和环境变量标记所引用的变量(关闭)。之后,剩余的标记变量被视为要删除的变量,因为环境中的变量无法再访问这些变量。最后,当垃圾回收机制在下一个周期运行时,这些变量的内存将被释放,它们所占用的空间将被回收。
目前 IE、Firefox、Opera、Chrome、Safari 的 js 实现均采用标记清除垃圾回收策略或类似策略,但垃圾回收间隔各不相同。
引用计数:
语言引擎有一个“引用表”,用于存储对内存中所有资源(通常是各种值)的引用次数。如果某个值的引用次数为0,则表示该值不再使用,可以释放这块内存。

上图中左下角的两个值没有被引用,所以可以释放。
如果一个值不再需要,但引用次数不为0,垃圾回收机制无法释放这块内存,导致内存泄漏。
const arr = [1,2,3,4];
console.log("hello world");上面代码中,数组[1,2,3,4]是一个值,会占用内存。变量arr是这个值的唯一引用,所以引用次数为1。下面的代码虽然没有使用arr,但是会继续占用内存。
如果添加一行代码去掉arr对[1,2,3,4]的引用,这块内存可以通过垃圾回收机制释放。
let arr = [1,2,3,4];
console.log("hello world");
arr = null;上面代码中,将arr重置为null会移除对[1, 2, 3, 4]的引用,引用次数变为0,可以释放内存。
所以,并不是说有了垃圾回收机制,程序员就轻松了。仍然需要注意内存占用:一旦占用大量空间的值不再使用,必须检查是否还有对它们的引用。如果是这样,您必须手动取消引用
内存泄漏的识别方法
如何观察内存泄漏?
经验法则是,如果连续五次垃圾回收后,内存使用量变得大于一次,就会发生内存泄漏。这需要实时查看内存使用情况。
1 个浏览器
在 Chrome 浏览器中检查内存使用情况并按照以下步骤操作。

打开开发者工具,选择Timeline面板,勾选上方Capture区域的Memory,点击左上角的record按钮。对页面进行各种操作,模拟用户的使用情况。一段时间后,点击对话框中的停止按钮,面板上会显示这段时间的内存使用情况。
如果内存使用情况基本稳定且接近该水平,则说明没有内存泄漏。

相反,存在内存泄漏。

2 命令行
命令行可以使用Node提供的方法。
console.log(process.memoryUsage());
// { rss: 27709440,
// heapTotal: 5685248,
// heapUsed: 3449392,
// external: 8772 }
process.memoryUsage 返回一个对象,其中包含 Node 进程的内存使用信息。该对象包含四个字段,单位为字节,含义如下。

判断内存泄漏,以heapUsed字段为准。
弱映射
如前所述,及时清除引用非常重要。但是,记不住那么多,有时候一不小心就忘了,所以内存泄漏就这么多了。
创建新引用时最好有方法声明,哪些引用必须手动清除,哪些引用可以忽略。当其他引用消失时,垃圾回收机制可以释放内存。这样可以大大减轻程序员的负担,只要清除主引用即可。

ES6 考虑到了这一点并引入了两种新的数据结构:和。它们对值的引用不包含在垃圾收集机制中,所以名称中会有一个“Weak”,表示这是一个弱引用。

下面以 WeakMap 为例,看看它是如何解决内存泄漏的。
const wm = new WeakMap();
const element = document.getElementById(example);
wm.set(element, some information);
wm.get(element) // "some information"
在上面的代码中,首先创建了一个 Weakmap 实例。然后,在实例中存储一个DOM节点作为键名,以及一些附加信息作为键值,一起存储在WeakMap中。此时对WeakMap中元素的引用是弱引用,不会被垃圾回收机制计算在内。
也就是说,DOM节点对象的引用计数是1,而不是2。这时候,一旦对节点的引用被清除,它所占用的内存就会被垃圾回收机制释放。 Weakmap 保存的键值对也会自动消失。
基本上,如果想在不干扰垃圾回收机制的情况下向对象添加数据,可以使用WeakMap。
弱映射示例
WeakMap 示例很难演示,因为无法观察到其中的引用会自动消失。这时候去掉了其他的引用,也没有再引用WeakMap的key名,导致无法验证key名是否存在。
直到有一天,何世君老师想出了一个办法,如果引用指向的值占用大量内存,可以通过process.memoryUsage方法查看。
根据这个想法,网友vtxf补充了以下内容。
首先,打开 Node 命令行。
$ node --expose-gc
上面代码中,--expose-gc参数表示允许手动执行垃圾回收机制。
然后,执行以下代码。
// 手动执行一次垃圾回收,保证获取的内存使用状态准确
> global.gc();
undefined
// 查看内存占用的初始状态,heapUsed 为 4M 左右
> process.memoryUsage();
{ rss: 21106688,
heapTotal: 7376896,
heapUsed: 4153936,
external: 9059 }
> let wm = new WeakMap();
undefined
> let b = new Object();
undefined
> global.gc();
undefined
// 此时,heapUsed 仍然为 4M 左右
> process.memoryUsage();
{ rss: 20537344,
heapTotal: 9474048,
heapUsed: 3967272,
external: 8993 }
// 在 WeakMap 中添加一个键值对,
// 键名为对象 b,键值为一个 5*1024*1024 的数组
> wm.set(b, new Array(5*1024*1024));
WeakMap {}
// 手动执行一次垃圾回收
> global.gc();
undefined
// 此时,heapUsed 为 45M 左右
> process.memoryUsage();
{ rss: 62652416,
heapTotal: 51437568,
heapUsed: 45911664,
external: 8951 }
// 解除对象 b 的引用
> b = null;
null
// 再次执行垃圾回收
> global.gc();
undefined
// 解除 b 的引用以后,heapUsed 变回 4M 左右
// 说明 WeakMap 中的那个长度为 5*1024*1024 的数组被销毁了
> process.memoryUsage();
{ rss: 20639744,
heapTotal: 8425472,
heapUsed: 3979792,
external: 8956 }
在上面的代码中,只要外部引用消失,WeakMap的内部引用就会被垃圾回收自动清除。这表明在它的帮助下,解决内存泄漏会简单得多。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-380815-1.html
桌子乱成那样
蔡要是敢