聊聊Javascript 垃圾回收机制-(一)

引子

我突然想起来了还有个博客帐号,最近工做任务较少,因此间歇性踌躇满志又更新一下-\_-javascript

正文

背景介绍-什么是垃圾回收

垃圾回收(garbage-collection)是指对已分配的内存进行回收,当咱们在建立一些变量,函数,对象时,例如:java

var a = "Hello";
var getName = function (){ //函数体}

都须要分配内存,而当这些值再也不被使用的时候,js就须要在合适的时候将这部分的内存进行回收,这就是垃圾回收机制,对于一些大型应用程序来讲,垃圾回收能够有效提升性能。在js里,执行垃圾回收是自动执行的,不对外提供任何接口,不过仍是有必要适当了解下它的原理。算法

回收条件-什么状况能够进行回收

了解完垃圾回收的概念之后,首要的问题就来了:对于已分配出去的内存,什么状况下能够进行垃圾回收呢?浏览器

联系下实际生活:咱们会把什么样的东西送去回收呢,那固然是肯定之后根本用不到的东西。闭包

在js里,也是同样,对于再也没法访问到的值,咱们就要进行回收。函数

通常案例

举个简单例子:post

var user = {
  name: 'Leo'
}; // 第一步,建立一个对象并把内存地址赋值给user
user = null;  // 第二步,修改user的内存地址

以前在其余文章已经说过,建立引用类型值的时候,赋值给变量的实质上是内存地址。执行上述代码的第一步以后,能够经过window.user访问到{name: 'Leo'}对象,性能

图片
可是在执行Leo = null以后,这个变量对象其实已经没法访问到了。也就达到了能够被回收的条件。优化

图片

再看个稍微复杂的案例:spa

var user = {
  name: 'Leo',
  friend: {
    name: 'John'
  }
}; 
// 接下来移除引用
user.friend = null;// 第一种状况,修改user.friend的地址 
user = null;// 第二种状况

这个例子中比前面多增长了一个对象{name: 'John'}, 移除引用先后对应的图以下:
图片

图片

互相引用

如今你们应该对什么叫作没法被访问有一个大概的概念了。 那么来个特殊一点的:

// '朋友圈'函数 让传入的两我的成为朋友,并返回这个小圈子
function circleOfFriends(user1, user2){
   user1.friend = user2;
   user2.friend = user1;
   return {
     user1,
     user2
   }
} 
var circle = circleOfFriends({name: 'Leo'},{name: 'John'});

执行这个以后,关系图以下:
图片

window能够经过circle访问到circle对象,circle能够经过user1`user2属性访问到{name: 'Leo'},{name: 'John'}`两个对象, 因此此时这三个对象都仍是可访问的。

若是接下来依次执行如下步骤:

circle.user1 = null; //此时若是要访问{name: 'Leo'}对象 还能够经过circle.user2.friend 来实现,因此它依然是能够访问到的

图片

接着执行:

circle.user2.friend = null; // 此时{name: 'Leo'}被完全孤立,不再可访问,它知足能够被回收的条件

图片

注意,从图中能够看到{name: 'Leo'}还有引用其余对象, 可是它自身是经过任何方式都没法被访问到的。因此此时它仍是属于可回收的。

固然,若是咱们不执行上面的语句,而是直接执行:

circle = null;

图片

此时,circle指向的对象,{name: 'Leo'}对象,{name: 'John'}对象,都将变得不可访问,即便他们之间内部是有互相引用关系。

到这里,咱们彷佛就能够概括出,知足可被回收的条件:

从全局对象出发,只要不可以直接或者间接被访问到的值,就知足可回收的条件;

真的仅仅是这样吗? 咱们彷佛还遗漏了一个重要的地方-- 执行环境。 曾几什么时候,咱们在介绍闭包的时候讲过执行环境做用域链, 很显然的,当前执行环境和做用域链上的值 也属于可访问的,例如最简单的函数例子:

var b = 2;
  function getA(){
    var a = 1;
      return a ;
  }
  getA();

当运行到getA()以前时,变量b是可访问的,可是变量a是不可访问的, 当执行getA函数内部时, 变量a就是能够访问的了,固然此时变量b也能够访问。
因此咱们须要完善一下上面的结论:

从当前执行环境以及做用域链上可访问的对象出发,任何能够被访问的对象,都算是可访问对象。

固然 若是认为执行环境也是当前全局对象可访问的对象之一,那同样能够用前面的简化版结论,表述方式不必太较真,重点是要记住执行环境和做用域链这个要点。

(有印象的同窗不妨思考下好久以前说过的闭包,思考下闭包和垃圾回收机制的关系,后面有机会也会继续补充说明)

垃圾回收的算法

标记清除法

其实上面的内容讲完, 标记清除法的思路,也就基本清楚了。 标记清除法,顾名思义分为2个步骤:

  1. 标记:从根节点(这里的根节点,指的就是上文提到的全局对象以及当前执行环境以及做用域链上可访问的对象)出发,深度优先遍历全部能够访问到的节点,并打上"可访问"的标记
  2. 清除:从全部节点里,清除掉没有打上标记的节点。

这里介绍的是核心思路,具体实践的时候有些地方是能够优化的,例如第一步标记的过程,对于某些节点有可能被重复遍历的,就像前面提到的'朋友圈'模型中,既能够经过circle访问其中的某个对象, 也能够经过某个userfriend属性访问到, 那么为了不重复遍历,咱们就能够另外用一种标记,来标识表示节点已经被遍历过,详细算法会在后续的系列文章介绍。

引用计数法

其实mdn还介绍了早期的另外一种回收算法,咱们也介绍提一下。

引用计数法顾名思义是就是先记下某个变量被引用的总次数,而后当被引用次数为0时,就表示可回收。

这个思路咋一看和标记清除法是很类似的,可是实际上有个很明显的区别,就是在前面互相引用的例子中:两个user其实都是不可得到的,可是因为互相引用,它们的被引用次数并不为0,那么按照引用计数法,这两个对象就不会被清除,这是有问题的, 因此从2012年起,全部的现代浏览器都是使用标记清除法。

总结

文本主要介绍一下js中垃圾回收机制和两种垃圾回收算法,但愿你们看完后对此有个简单的概念,其中比较重要的是辨析各类知足垃圾回收机制的情景,还有就是对象有被引用不等于可访问(互相引用)关于垃圾回收机制其实还有蛮多的问题须要探索,例如:

  1. 标记清除法的具体实现
  2. 垃圾回收执行的时机和频率是怎么样的
  3. 有哪些优化机制保证清除效率

会在此系列的后续文章上进一步更新说明。(应该会吧~)

惯例:若是内容有错误的地方欢迎指出(以为看着不理解不舒服想吐槽也彻底没问题);若是有帮助,欢迎点赞和收藏,转载请征得赞成后著明出处,若是有问题也欢迎私信交流,主页有邮箱地址

而后还有一些想说的就是,很感谢你们的关注和一些私信,尤为是看到一些读者说看了文章之后确实有帮助的,会以为很感动。 其实本身也满惭愧的,每次更新都间好久,确实有点太懒了~, 之后会尽可能勤快一些。

顺便再说下,RingCentral目前在杭州也设置了办公点,并且能够申请长期远程办公,帮你告别996,工做生活两不误,有兴趣的同窗能够私信或者发邮件给我,能够免费帮忙内推~

参考文章

http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection

https://javascript.info/garbage-collection

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory\_Management

相关文章
相关标签/搜索