什么是 JavaScript 垃圾回收?

IT技术 javascript garbage-collection
2021-01-23 08:59:43

什么是 JavaScript 垃圾回收?为了编写更好的代码,Web 程序员了解 JavaScript 垃圾收集有什么重要意义?

6个回答

Eric Lippert 不久前写了一篇关于这个主题详细博客文章(另外将它与VBScript进行比较)。更准确地说,他写了JScript,这是微软自己的 ECMAScript 实现,虽然与 JavaScript 非常相似。我想您可以假设 Internet Explorer 的 JavaScript 引擎的绝大多数行为都是相同的。当然,实现会因浏览器而异,但我怀疑您可以采用一些通用原则并将它们应用于其他浏览器。

引自该页面:

JScript 使用非分代标记和清除垃圾收集器。它是这样工作的:

  • “范围内”的每个变量都称为“清道夫”。清道夫可以指一个数字、一个对象、一个字符串,等等。我们维护一个清道夫列表——变量在进入作用域时移到 scav 列表中,当它们离开作用域时移出 scav 列表。

  • 垃圾收集器时不时地运行。首先,它在每个对象、变量、字符串等上放置一个“标记”——GC 跟踪的所有内存。(JScript 在内部使用 VARIANT 数据结构,并且该结构中有大量额外未使用的位,因此我们只设置其中之一。)

  • 其次,它清除了清道夫和清道夫引用的传递闭包上的标记。因此,如果一个清道夫对象引用了一个非清道夫对象,那么我们清除非清道夫对象上的位,以及它所引用的一切。(我使用“闭包”这个词的意义与我之前的帖子不同。)

  • 在这一点上,我们知道所有仍然标记的内存都是分配的内存,任何范围内变量的任何路径都无法到达这些内存。所有这些对象都被指示自行拆除,这会破坏任何循环引用。

垃圾收集的主要目的是让程序员不必担心他们创建和使用的对象的内存管理,当然有时也无法避免它 - 至少对垃圾收集的工作原理有一个粗略的了解总是有益的.

历史记录:答案的较早修订版对delete操作员的引用不正确在 JavaScript 中delete运算符从对象中删除一个属性,这deleteC/C++ 中的完全不同

ECMAScriptdelete是一元运算符(表达式),而不是语句(即:)delete 0, delete 0, delete 3当用表达式语句表示时,它看起来像语句。
2021-03-12 08:59:43
请注意,Eric 的文章应被视为“仅用于历史目的”。但它仍然是信息性的。
2021-03-21 08:59:43
苹果指南有缺陷:作者使用delete不当;例如,在第一个示例中delete foo,您应该首先删除事件侦听器 via window.removeEventListener(),然后使用foo = null覆盖变量,而不是在 IE 中,delete window.foo(但不是delete foo)如果foo是全局的,也会起作用,但即使如此,在 FF 或 Opera 中也不会
2021-03-30 08:59:43
是的,当时的答案现在已经过时了,截至 2012 年,现代浏览器使用标记/扫描算法......所以它不再依赖于范围。参考:developer.mozilla.org/en-US/docs/Web/JavaScript/...
2021-03-30 08:59:43
另请注意 - IE 6 和 7 不使用非分代标记和清除垃圾收集器。他们使用一个简单的引用计数垃圾收集器,它更容易受到垃圾收集循环引用问题的影响。
2021-03-31 08:59:43

当涉及 DOM 对象时,请注意循环引用:

JavaScript 中的内存泄漏模式

请记住,只有在没有对对象的活动引用时才能回收内存。这是闭包和事件处理程序的常见陷阱,因为一些 JS 引擎不会检查内部函数中实际引用了哪些变量,而只保留封闭函数的所有局部变量。

这是一个简单的例子:

function init() {
    var bigString = new Array(1000).join('xxx');
    var foo = document.getElementById('foo');
    foo.onclick = function() {
        // this might create a closure over `bigString`,
        // even if `bigString` isn't referenced anywhere!
    };
}

bigString只要事件处理程序存在,一个幼稚的 JS 实现就无法收集有几种方法来解决这个问题,比如设置bigString = null在年底init()delete将局部变量和函数参数不工作:delete从对象中移除了属性和变量对象是不可访问- ES5严格模式下甚至会抛出ReferenceError,如果你尝试删除局部变量!)。

如果您关心内存消耗,我建议尽可能避免不必要的关闭。

2021-03-14 08:59:43
DOM 循环引用错误是 JScript 特有的——除 IE 之外,没有其他浏览器会受到影响。事实上,我相当确定 ECMAScript 规范明确指出 GC 必须能够处理这样的循环:-/
2021-03-17 08:59:43
@olliej:我在ECMAScript 规范中没有看到任何关于 GC 的提及
2021-03-21 08:59:43

来自博客的好引述

DOM 组件和 JScript 组件都是“垃圾收集”的,这意味着如果您在任一组件中创建一个对象,然后失去对该对象的跟踪,它最终将被清除。

例如:

function makeABigObject() {
var bigArray = new Array(20000);
}

当您调用该函数时,JScript 组件会创建一个可在该函数内访问的对象(名为 bigArray)。但是,一旦函数返回,您就“失去了对 bigArray 的跟踪”,因为无法再引用它。好吧,JScript 组件意识到您已经失去了对它的跟踪,因此 bigArray 被清理——它的内存被回收。同样的事情在 DOM 组件中起作用。如果你说document.createElement('div'),或者类似的话,那么 DOM 组件会为你创建一个对象。一旦您以某种方式失去了对该对象的跟踪,DOM 组件将清理相关的。

据我所知,JavaScript 的对象在没有剩余对对象的引用时会定期进行垃圾回收。这是自动发生的事情,但如果你想了解更多关于它是如何工作的,在 C++ 级别,查看WebKitV8 源代码是有意义的

通常,您不需要考虑它,但是,在较旧的浏览器中,例如 IE 5.5 和 IE 6 的早期版本,也许是当前版本,闭包会创建循环引用,如果未选中该引用,则最终会消耗内存。在我的意思是关于闭包的特殊情况下,是当您添加对 dom 对象的 JavaScript 引用,以及向引用回 JavaScript 对象的 DOM 对象添加对象时。基本上它永远不会被收集,最终会导致操作系统在循环创建崩溃的测试应用程序中变得不稳定。在实践中,这些泄漏通常很小,但为了保持代码干净,您应该删除对 DOM 对象的 JavaScript 引用。

通常,使用 delete 关键字立即取消引用大对象(如您收到的 JSON 数据)并完成您需要对其进行的任何操作是一个好主意,尤其是在移动 Web 开发中。这会导致 GC 的下一次扫描删除该对象并释放其内存。

只是传闻。我没有注意到在标准模式下运行的 IE 8 中的疯狂泄漏,而不是损坏模式。我会调整我的反应。
2021-03-13 08:59:43
@erikkallen:是的,IE 版本 8+ 中的 GC 错误已得到修复,因为较旧的版本使用了一种非常幼稚的垃圾收集算法,这使得无法对一对相互引用的对象进行 GC。较新的mark-and-sweep风格算法会处理这个问题
2021-03-13 08:59:43
较新版本的 IE 是否解决了 JavaScript -> DOM -> JavaScript 循环引用问题?如果有,从什么时候开始?我认为它在架构上非常深入,不太可能得到修复。你有消息来源吗?
2021-04-08 08:59:43

垃圾回收 (GC) 是一种通过删除不再需要的对象来进行自动内存管理的形式。

任何处理内存的进程都遵循以下步骤:

1 - 分配你需要的内存空间

2 - 做一些处理

3 - 释放此内存空间

有两种主要算法用于检测不再需要的对象。

引用计数垃圾收集:该算法将“不再需要一个对象”的定义简化为“一个对象没有其他对象引用它”,如果没有引用指向它,该对象将被删除

标记和扫描算法:将每个对象连接到根源。任何对象都不会连接到根或其他对象。此对象将被删除。

目前大多数现代浏览器都使用第二种算法。

要添加此源,请参阅 MDN:developer.mozilla.org/en-US/docs/Web/JavaScript/...
2021-03-27 08:59:43