JS内存泄漏与垃圾回收机制

内存生命周期:

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放\归还

一、什么是内存泄漏?

程序的运行需要内存,只要程序提出要求,操作系统或者运行是就必须供给内存。

对于持续运行的服务进程,必须及时释放内存,否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。

  • 不再用到的内存,没有及时释放,就叫做内存泄漏。
  • 本质上讲, 内存泄露就是不再被需要的内存, 由于某种原因, 无法被释放。

内存泄露案例:全局变量、未销毁的定时器和回调函数(setInterval)、闭包(外部函数的变量被引用,得不到释放)、DOM 引用(移除了元素,但是仍然有对 元素的引用)


二、javascript垃圾回收机制原理:

JavaScript具备自动垃圾收集机制,执行环境会负责管理代码执行过程当中使用的内存。也就是说,所需内存的分配和无用内存的回收彻底实现了自动管理,这被称为"垃圾回收机制"。

垃圾回收机制的原理:垃圾收集器会按照固定的时间间隔或代码执行中预约的收集时间,周期性地执行如下操做——找出再也不继续使用的变量,而后释放其占用的内存。

用于标识无用变量的方式有两种:标记清除法和引用计数法。

2.1 标记清除法:spa
  • JavaScript最经常使用的垃圾收集方式。

当变量进入环境时,这个变量标记为“进入环境”;而当变量离开环境时,则将其标记为“离开环境”。

可使用一个“进入环境”的变量列表及一个“离开环境”的变量列表来跟踪变量的变化,也能够翻转某个特殊的位来记录一个变量什么时候进入环境及离开环境。

2.2 引用计数法:code
  • 不太常见的垃圾收集策略。引用计数的含义是跟踪记录每一个值被引用的次数。

当声明了一个变量并将一个引用类型值赋给该变量时,则该值的引用次数就是1;若是同一个值又被赋给另外一个变量,则该值的引用次数加1;若是包含对该值引用的变量又取得了另一个值,则该值的引用次数减1。当该值的引用次数变为0时,则能够回收其占用的内存空间。当垃圾回收器下一次运行时,就会释放那些引用次数为0的值所占用的内存。

循环引用产生的问题:

现象: 对象A包含一个指向对象B的指针,而对象B中也包含一个指向A的指针。

弊端:采用引用计数策略时,函数执行后,两个对象还将继续存在,由于它们的引用次数永远不会为0。

解决:手动断开两个对象之间的相互引用。

解除引用:一旦数据再也不有用,最好经过将其设置为null来释放其引用。

  • 解除一个值的引用不意味着自动回收该值所占用的内存,解除引用的真正做用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

三、内存泄漏的识别方法

怎样可以观察到内存泄漏呢?

经验法则:如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。这就要求实时查看内存占用。

3.1 浏览器
  • Chrome 浏览器查看内存占用的方法。

按照以下步骤操作:

  1. 打开开发者工具,选择 Timeline 面板
  2. 在顶部的Capture字段里面勾选 Memory
  3. 点击左上角的录制按钮。
  4. 在页面上进行各种操作,模拟用户的使用情况。
  5. 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。
  • 如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。
  • 反之,就是内存泄漏了。
3.2 命令行
  • 命令行可以使用 Node 提供的process.memoryUsage方法。
//判断内存泄漏,以heapUsed字段为准。
console.log(process.memoryUsage());
// { rss: 27709440,      rss(resident set size):所有内存占用,包括指令区和堆栈。
// heapTotal: 5685248,   heapTotal:"堆"占用的内存,包括用到的和没用到的。
// heapUsed: 3449392,    heapUsed:用到的堆的部分。
// external: 8772 }      external: V8 引擎内部的 C++ 对象占用的内存。
process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。
3.3 WeakMap

前面说过,及时清除引用非常重要。但是,你不可能记得那么多,有时候一疏忽就忘了,所以才有那么多内存泄漏。

在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以忽略不计,当其他引用消失以后,垃圾回收机制就可以释放内存。这样就能大大减轻程序员的负担,你只要清除主要引用就可以了。

  • ES6 考虑到了这一点,推出了两种新的数据结构:WeakSet 和 WeakMap。
    • WeakMap的键名所指向的对象,不计入垃圾回收机制。
  • 它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。。
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
  1. 上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。

  2. 也就是说,DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。

  3. 基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。

你可能感兴趣的:(JS内存泄漏与垃圾回收机制)