浅谈一下js的垃圾回收的内容

不言
不言 原创
2018-07-16 09:42:22 1845浏览

这篇文章主要介绍了关于js的垃圾回收机制,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

垃圾回收

内存管理于我们来说是自动的、不可见的。我们创建的原始类型、对象、函数等等,都会占用内存。

当它们不被需要之后会发生什么?JavaScript 引擎要如何发现并清除他们?

可触及(Reachability)

JavaScript 内存管理的关键概念是可触及(Reachability)

简单来说,“可触及”的值就是可访问的,可用的,他们被安全储存在内存。

  1. 以下是一些必定“可触及”的值,不管出于任何原因,都不能删除:

  • 当前函数的局部变量和参数。

  • 当前调用链(current chain of nested calls)中所有函数的局部变量和参数。

  • 全局变量。

  • (以及其他内部变量)

这些值都称为 *roots*。
  1. 其他值是否可触及视乎它是否被 root 及其引用链引用。

    假设有一个对象存在于局部变量,它的值引用了另一个对象,如果这个对象是可触及的,则它引用的对象也是可触及的,后面会有详细例子。

JavaScript 引擎有一个 垃圾回收) 后台进程。它监控着所有对象,当对象不可触及时会将其删除。

一个简单例子

// user has a reference to the object
let user = {
  name: "John"
};

2566786521-5b4b1e03a7eb8_articlex[1].png

箭头代表的是对象引用。全局变量 "user" 引用了对象{name: "John"}(简称此对象为 John)。John 的 "name" 属性储存的是一个原始值,所以无其他引用。

如果覆盖 user,对 John 的引用就丢失了:

user = null;

155327879-5b4b1e039f60c_articlex[1].png

现在 John 变得不可触及,垃圾回收机制会将其删除并释放内存。

两个引用

如果我们从 user 复制引用到 admin

// user has a reference to the object
let user = {
  name: "John"
};

*!*
let admin = user;
*/!*

1546865928-5b4b1e038fe5e_articlex[1].png

如果重复一次这个操作:

user = null;

……这个对象是依然可以通过 admin 访问的,所以它依然存在于内存。如果我们把 admin 也覆盖为 null,那它就会被删除了。

相互引用的对象

这个例子比较复杂:

function marry(man, woman) {
  woman.husband = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman
  }
}

let family = marry({
  name: "John"
}, {
  name: "Ann"
});

marry 函数让两个参数对象互相引用,返回一个包含两者的新对象,结构如下:

1941061787-5b4b1e0308cd3_articlex[1].png

暂时所有对象都是可触及的,但我们现在决定移除两个引用:

delete family.father;
delete family.mother.husband;

1604544661-5b4b1e030276c_articlex[1].png

只删除一个引用不会有什么影响,但是两个引用同时删除,我们可以看到 John 已经不被任何对象引用了:

4016353222-5b4b1e030b7a0_articlex[1].png

即使 John 还在引用别人,但是他不被别人引用,所以 John 现在已经是不可触及的了,它的存在将会被移除。

垃圾回收后:

2369010816-5b4b1e02ecc22_articlex[1].png

孤岛(Unreachable island)

也可能有一大堆互相引用的对象整块(像个孤岛)都不可触及了。

对上面的对象进行操作:

family = null;

内存中的情况如下:

317280959-5b4b1e02da6a3_articlex[1].png

这个例子展示了“可触及”这个概念的重要性。

尽管 John 和 Ann 互相依赖,但这仍不足够。

"family" 对象整个已经切断了与 root 的连接,没有任何东西引用到这里,所以这个孤岛遥不可及,只能等待被删除。

内部算法

基础的垃圾回收算法被称为“标记-清除算法”("mark-and-sweep"):

  • 垃圾回收器获取并标记 root。

  • 然后访问并标记来自他们的所有引用。

  • 访问被标记的对象,标记他们的引用。所有被访问过的对象都会被记录,以后将不会重复访问同一对象。

  • ……直到只剩下未访问的引用。

  • 所有未被标记的对象都会被移除。

假设对象结构如下:

862819114-5b4b1e0218952_articlex[1].png

我们清晰地看到右边的“孤岛”。现在使用“标记-清除”的方法处理他。

第一步,标记 root:

3725254996-5b4b1e0218f2b_articlex[1].png

然后标记他们的引用:

3567439851-5b4b1e022afb7_articlex[1].png

……标记他们引用的引用:

464038720-5b4b1e0226abc_articlex[1].png

现在没有被访问过的对象会被认为是不可触及,他们将会被删除:

2993400725-5b4b1e022dacc_articlex[1].png

这就是垃圾回收的工作原理。

JavaScript 引擎在不影响执行的情况下做了很多优化,使这个过程更垃圾回收效率更高:

  • 分代收集 -- 对象会被分为“新生代”和“老生代”。很多对象完成任务后很快就不再需要了,所以对于他们的清理可以很频繁。而在清理中留下的称为“老生代”一员。

  • 增量收集 -- 如果对象很多,很难一次标记完所有对象,这个过程甚至对程序执行产生了明显的延迟。所以引擎会尝试把这个操作分割成多份,每次执行一份。这样做要记录额外的数据,但是可以有效降低延迟对用户体验的影响。

  • 闲时收集 -- 垃圾回收器尽量只在 CPU 空闲时运行,减少对程序执行的影响。

除此以外还有很多对垃圾回收的优化,在此就不详细说了,各个引擎有自己的调整和技术,而且这个东西一直随着引擎的更新换代在改变,如果不是有实在的需求,不值得挖太深。不过如果你真的对此有浓厚的兴趣,下面会为你提供一些拓展链接。

总结

重点:

  • 垃圾回收自动进行,我们不能强制进行或阻止他。

  • 可触及的对象会被保留在内存中。

  • 被引用不一定是可触及的(从 root):相互引用的对象可能整块都是不可触及的。

现代引擎实现了加强版的垃圾回收算法,《The Garbage Collection Handbook: The Art of Automatic Memory Management》(R. Jones 等)一书中提及了他们。

如果你熟悉底层编程,可以阅读 A tour of V8: Garbage Collection 了解更多关于 V8 垃圾回收的细节。

V8 blog 也会经常发布一些关于内存管理的文章。学习垃圾回收算法最好还是先学习 V8 的实现,阅读 Vyacheslav Egorov(V8 工程师之一)的博客。我说 V8 是因为在互联网上关于 V8 的文章比较多。对于其他引擎,很多实现都是相似的,但是垃圾回收算法上区别不少。

对引擎的深入理解在做底层优化的时候很有帮助。在你熟悉一门语言之后,这是一个明智的研究方向。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

如何通过js判断页面在pc端打开还是移动端打开

对JS中的prototype的详解

以上就是浅谈一下js的垃圾回收的内容的详细内容,更多请关注php中文网其它相关文章!

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。