js垃圾回收机制和内存泄漏

知识线

什么是内存泄漏?=> 是什么导致的内存泄漏?=> 怎么解决内存泄漏?=> 垃圾回收机制的策略(两种) => 如何管理好内存?

面试官:什么是内存泄漏?为什么会导致内存泄漏?

什么是内存泄漏?

不再用到的内存,没有及时释放,就叫做内存泄漏。

为什么会导致内存泄漏?

内存泄漏是指我们已经无法再通过 js 代码来引用到某个对象,但垃圾回收器却认为这个对象还在被引 用,因此在回收的时候不会释放它。

导致了分配的这块内存永远也无法被释放出来。如果这样的情况越 来越多,会导致内存不够用而系统崩溃。

垃圾回收机制

JavaScript 具有自动垃圾收集机制(GC:GarbageCollecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。开发人员不用再关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。

面试官:怎么解决内存泄漏?说一说 JS 垃圾回收机制的运行机制的原理?

需要我们手动管理好内存,但是对于 JS 有自动垃圾回收机制,自动对内存进行管理。

内存生命周期

JS环境中分配的内存一般有如下生命周期:

  1. 内存分配:当我们申明变量、函数、对象,并执行的时候,系统会自动为他们分配内存
  2. 内存使用:即读写内存,也就是使用变量、函数等
  3. 内存回收:使用完毕,由垃圾回收机制自动回收不再使用的内存
垃圾回收机制策略
标记清除算法

JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep)。

它的实现原理就是通过判断一个变量是否在执行环境中被引用,来进行标记删除。

垃圾回收器给所有内存变量进行标记,然后去掉执行环境中和被引用的标记,剩余的标记变量是 不会被用到的变量,会被垃圾回收器回收。

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

该算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始(在JS中就是全局对象)扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

此算法可以分为两个阶段,一个是标记阶段(mark),一个是清除阶段(sweep)。

  1. 标记阶段,垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。
  2. 清除阶段,垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。

js垃圾回收机制和内存泄漏_第1张图片

在标记阶段,从根对象1可以访问到B,从B又可以访问到E,那么B和E都是可到达对象,同样的道理,F、G、J和K都是可到达对象。

在回收阶段,所有未标记为可到达的对象都会被垃圾回收器回收。

何时开始垃圾回收?

通常来说,在使用标记清除算法时,未引用对象并不会被立即回收。取而代之的做法是,垃圾对象将一直累计到内存耗尽为止。当内存耗尽时,程序将会被挂起,垃圾回收开始执行。

补充: 从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。

标记清除算法缺陷
  • 那些无法从根对象查询到的对象都将被清除
  • 垃圾收集后有可能会造成大量的内存碎片,像上面的图片所示,垃圾收集后内存中存在三个内存碎片,假设一个方格代表1个单位的内存,如果有一个对象需要占用3个内存单位的话,那么就会导致Mutator一直处于暂停状态,而Collector一直在尝试进行垃圾收集,直到Out of Memory。

引用计数算法

这是最初级的垃圾收集算法.现在已经没有浏览器会用这种算法.

此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

引用计数缺陷

该算法有个限制:无法处理循环引用。如果两个对象被创建,并互相引用,形成了一个循环。它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。

Chrome V8回收算法

参考 Chrome V8回收算法

减少垃圾和回收对性能的影响:

主要注意以下两点:

  • 让垃圾回收尽量少地进行,尤其是全堆垃圾回收。这部分我们基本上帮不了什么忙,主要靠v8自己的优化机制.
  • 避免内存泄露,让内存及时得到释放. 即下面要讲的如何管理内存,避免内存泄漏

如何管理内存

JS 的内存自动管理一个最主要的问题就是分配给 Web 浏览器的可用内存数量通常比分配给桌面应用程 序的少。

避免策略:
  1. 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收(即赋值为null);
  2. 注意程序逻辑,避免“死循环”之类的 ;
  3. 避免创建过多的对象 原则:不用了的东西要记得及时归还。
  4. 减少层级过多的引用

为了能够让页面获得最好的性能,必须确保 js 变量占用最少的内存,最好的方式就是将不用的变量引用 释放掉,也叫做解除引用。

  • 局部变量:函数执行完成离开环境变量,变量将自动解除。
  • 全局变量:对于全局变量我们需要进行手动解除。(注意:解除引用并不意味被收回,而是将变量 真正的脱离执行环境,下一次垃圾回收将其收回)
var a = 20; // 在栈内存中给数值变量分配空间
alert(a + 100); // 使用内存
var a = null; // 使用完毕之后,释放内存空间

补充:因为通过上边的垃圾回收机制的标记清除法的原理得知,只有与环境变量失去引用的 变量才会被标记回收,所用上述例子通过将对象的引用设置为 null ,此变量也就失去了引 用,等待被垃圾回收器回收。

你可能感兴趣的:(前端,#,JS,内存泄漏,内存管理,垃圾回收,javascript)