<p>最近看到一些面试的回顾,不少有被面试官问到谈谈JS 垃圾回收机制,说实话,面试官会问这个问题,说明他最近看到一些关于 JS 垃圾回收机制的相关的文章,为了 B 格,就会顺带的问问。</p><p>最近看到一篇讲 JS 垃圾回收的国外文章,觉得讲得明白,所以就翻译过来了,希望对你们有所帮助。</p><p><strong>垃圾回收</strong></p><p>JavaScript 中的内存管理是自动执行的,而且是不可见的。我们创建基本类型、对象、函数……所有这些都需要内存。</p><p>当不再需要某样东西时会发生什么? JavaScript 引擎是如何发现并清理它?</p><p><strong>可达性</strong></p><p>JavaScript 中内存管理的主要概念是可达性。</p><p>简单地说,“可达性” 值就是那些以某种方式可访问或可用的值,它们被保证存储在内存中。</p><p>1. 有一组基本的固有可达值,由于显而易见的原因无法删除。例如:</p><ul style="list-style-type: disc;"><li><p>本地函数的局部变量和参数</p></li><li><p>当前嵌套调用链上的其他函数的变量和参数</p></li><li><p>全局变量</p></li><li><p>还有一些其他的,内部的</p></li></ul><p>这些值称为根。</p><p>2. 如果引用或引用链可以从根访问任何其他值,则认为该值是可访问的。</p><p>例如,如果局部变量中有对象,并且该对象具有引用另一个对象的属性,则该对象被视为可达性, 它引用的那些也是可以访问的,详细的例子如下。</p><p>JavaScript 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。</p><p><strong>一个简单的例子</strong></p><p>下面是最简单的例子:</p><p>// user 具有对象的引用</p><pre class="brush:js;toolbar:false">let user = { name: "John"};</pre><p style="text-align: center;"><img src="/upload/content/20200625/1593055997342188.jpg"/></p><p>这里箭头表示一个对象引用。全局变量“user”引用对象 {name:“John”} (为了简洁起见,我们将其命名为John)。John 的 “name” 属性存储一个基本类型,因此它被绘制在对象中。</p><p>如果 user 的值被覆盖,则引用丢失:</p><pre>user = null;</pre><p style="text-align: center;"><img src="/upload/content/20200625/1593055997909991.jpg"/></p><p>现在 John 变成不可达的状态,没有办法访问它,没有对它的引用。垃圾回收器将丢弃 John 数据并释放内存。</p><p><strong>两个引用</strong></p><p>现在让我们假设我们将引用从 user 复制到 admin:</p><p>// user具有对象的引用</p><pre class="brush:js;toolbar:false">let user = { name: "John"}; let admin = user;</pre><p style="text-align: center;"><img src="/upload/content/20200625/1593056012825482.jpg"/></p><p>现在如果我们做同样的事情:</p><pre class="brush:js;toolbar:false">user = null;</pre><p>该对象仍然可以通过 admin 全局变量访问,所以它在内存中。如果我们也覆盖admin,那么它可以被释放。</p><p>相互关联的对象</p><p>现在来看一个更复杂的例子, family 对象:</p><pre class="brush:js;toolbar:false">function marry (man, woman) { woman.husban = man; man.wife = woman; return { father: man, mother: woman } } let family = marry({ name: "John" }, { name: "Ann" })</pre><p>函数 marry 通过给两个对象彼此提供引用来“联姻”它们,并返回一个包含两个对象的新对象。</p><p>产生的内存结构:</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056041749544.jpg"/></p><p>到目前为止,所有对象都是可访问的。</p><p>现在让我们删除两个引用:</p><pre class="brush:js;toolbar:false">delete family.father; delete family.mother.husband;</pre><p style="text-align: center;"><img src="/upload/content/20200625/1593056049119316.jpg"/></p><p>仅仅删除这两个引用中的一个是不够的,因为所有对象仍然是可访问的。</p><p>但是如果我们把这两个都删除,那么我们可以看到 John 不再有传入的引用:</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056058959566.jpg"/></p><p>输出引用无关紧要。只有传入的对象才能使对象可访问,因此,John 现在是不可访问的,并将从内存中删除所有不可访问的数据。</p><p>垃圾回收之后:</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056079970645.jpg"/></p><p>无法访问的数据块</p><p>有可能整个相互连接的对象变得不可访问并从内存中删除。</p><p>源对象与上面的相同。然后:</p><pre class="brush:js;toolbar:false">family = null;</pre><p>内存中的图片变成:</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056093940074.jpg"/></p><p>这个例子说明了可达性的概念是多么重要。</p><p>很明显,John和Ann仍然链接在一起,都有传入的引用。但这还不够。</p><p>“family”对象已经从根上断开了链接,不再有对它的引用,因此下面的整个块变得不可到达,并将被删除。</p><p><strong>内部算法</strong></p><p>基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:</p><p>垃圾回收器获取根并“标记”(记住)它们。</p><p>然后它访问并“标记”所有来自它们的引用。</p><p>然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。</p><p>以此类推,直到有未访问的引用(可以从根访问)为止。</p><p>除标记的对象外,所有对象都被删除。</p><p>例如,对象结构如下:</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056105516016.jpg"/></p><p>我们可以清楚地看到右边有一个“不可到达的块”。现在让我们看看“标记并清除”垃圾回收器如何处理它。</p><p>第一步标记根</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056113443578.jpg"/></p><p>然后标记他们的引用</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056130622380.jpg"/></p><p>以及子孙代的引用:</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056145445969.jpg"/></p><p>现在进程中不能访问的对象被认为是不可访问的,将被删除:</p><p style="text-align: center;"><img src="/upload/content/20200625/1593056154118684.jpg"/></p><p>这就是垃圾收集的工作原理。JavaScript引擎应用了许多优化,使其运行得更快,并且不影响执行。</p><p>一些优化:</p><p>分代回收——对象分为两组:“新对象”和“旧对象”。许多对象出现,完成它们的工作并迅速结 ,它们很快就会被清理干净。那些活得足够久的对象,会变“老”,并且很少接受检查。</p><p>增量回收——如果有很多对象,并且我们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有一定的延迟。因此,引擎试图将垃圾回收分解为多个部分。然后,各个部分分别执行。这需要额外的标记来跟踪变化,这样有很多微小的延迟,而不是很大的延迟。</p><p>空闲时间收集——垃圾回收器只在 CPU 空闲时运行,以减少对执行的可能影响。</p><p><strong>面试怎么回答</strong></p><p>1)问什么是垃圾</p><p>一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。</p><p>2)如何检垃圾</p><p>一种算法是标记 标记-清除 算法,还想说出不同的算法可以参考这里。</p><p>更深入一些的讲解 http://newhtml.net/v8-garbage...</p><p>还有一种牛逼的答法就是说看我的博客,当然是要自己总结的博客。</p><p>代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。</p>