JavaScript 中的活跃度是什么?

What is Liveness in JavaScript?

为了研究 JavaScript GC 的复杂性,我深入研究了杂草(即 ECMAScript 规范)。我发现一个对象只要被认为是“活的”就不应该被收集。而liveness本身定义如下:

At any point during evaluation, a set of objects S is considered live if either of the following conditions is met:

  • Any element in S is included in any agent's [[KeptAlive]] List.
  • There exists a valid future hypothetical WeakRef-oblivious execution with respect to S that observes the Object value of any object in S.

一旦创建了一个特殊的WeakRef[[KeptAlive]]列表就会附加一个对象,它(弱)引用它,并在当前同步作业停止后清空。 但是,至于 WeakRef-oblivious execution,我想不明白它是什么:

For some set of objects S, a hypothetical WeakRef-oblivious execution with respect to S is an execution whereby the abstract operation WeakRefDeref of a WeakRef whose referent is an element of S always returns undefined.

WeakRefDeref of a WeakRef returns undefined 当其所指对象已被收集时。我说得对吗,这里暗示应该收集构成 S 的所有对象?所以 future 假设的 WeakRef-oblivious 执行的概念是仍然有一个对象,S 的一个元素,它还没有被一些 WeakRef 收集和观察].

这一切对我来说仍然毫无意义。我会很感激一些样品。

让我们忽略形式化但不完整的定义。我们在that section.1

的非规范注释中找到实际含义

What is Liveness in JavaScript?

Liveness 是保证哪个 WeakRefs 引擎不能为空的下限(注释 6)。因此,活动(组)对象是那些不能被垃圾收集的对象,因为它们仍将被程序使用。

但是,一组对象的存活性并不意味着所有集合中的对象必须被保留。这意味着集合中有 一些 对象仍将被程序使用,并且活动集合(作为一个整体)不能被垃圾收集。这是因为定义在 garbage collector Execution algorithm2 中以否定形式使用: 在任何时候,如果一组对象 S未生效,ECMAScript 实现可能3 […] 原子地[删除它们]。换句话说,如果实现选择一个非活动集S来清空 WeakRefs,它必须同时清空 S 中所有对象的 WeakRefs(注意2).

查看单个对象,如果至少有一个包含它们的非活动集合,我们可以说它们不是活动的(垃圾可收集);反之,我们说一个单独的对象是活的,如果包含它的每组对象都是活的(注 3)。这有点奇怪,因为“活动对象集”基本上被定义为“其中任何一个活动的一组对象”,但是个体活动总是“相对于集合S”,即这些对象是否可以一起被垃圾回收

1:这绝对是整个规范中注释内容比最高的部分。
2:强调我的
3:来自objectives的第一段:“本规范不保证任何对象都会被垃圾收集。不活动的对象可能会在很长时间后被释放一段时间,或者根本不会。因此,本规范在描述由垃圾收集触发的行为时使用术语“可能”。


现在,让我们试着理解这个定义。

At any point during evaluation, a set of objects S is considered live if either of the following conditions is met:

  • Any element in S is included in any agent's [[KeptAlive]] List.
  • There exists a valid future hypothetical WeakRef-oblivious execution with respect to S that observes the Object value of any object in S.

第一个条件很清楚。 agent is representing the list of objects to be kept alive until the end of the current Job. It is cleared after a synchronous run of execution ends, and the note on WeakRef.prototype.deref4 provides further insight on the intention: If [WeakRefDeref] return 的 [[KeptAlive]] 列表不是 undefinedtarget 对象,那么这个 target 对象不应该被垃圾回收,直到当前 ECMAScript 代码的执行已经完成。

不过第二个条件哦。没有明确定义“有效”、“未来执行”和“观察对象值”的含义。 上面的第二个条件旨在捕捉的直觉是,如果一个对象的身份可以通过非 WeakRef 方式观察到,那么该对象就是活的(注 2),啊哈。根据我的理解,“执行”是代理执行 JavaScript 代码以及在此期间发生的操作。如果它符合 ECMAScript 规范,它就是“有效的”。如果从程序的当前状态开始,它就是“未来”。
可以通过观察对象之间的严格相等比较或观察对象在 Map 中用作键来观察对象的身份(注释 4),据此我假设该注释仅提供示例和“对象值”的意思是“身份”。似乎重要的是代码是否关心或不关心是否使用了特定对象,并且所有这些仅在执行结果为 observable 时(即如果不更改 [=194= 就无法优化) ] 的程序)5.
要通过这些方法确定对象的活跃度,需要测试所有可能的未来执行,直到对象不再可观察为止。因此,这里定义的活跃度是 undecidable6. In practice, engines use conservative approximations such as reachability7 (note 6), but notice that research on more advanced garbage-collectors 正在进行中。

现在有趣的是:是什么让执行“相对于一组对象 S 假设的 WeakRef-oblivious”?这意味着在 S 中对象的所有 WeakRefs 都已被清除 8 的假设下执行。我们假设在未来的执行过程中,抽象操作WeakRefDerefWeakRef 的引用对象是 S 的元素总是 returns undefined (def),然后返回它是否仍然可以观察集合中的元素。 如果弱引用清除后none个待观察对象可以被垃圾回收否则S被认为是存活的, 对象不能被垃圾回收并且不能清除对它们的弱引用。

4: 例子见全文。有趣的是,new WeakRef(obj) 构造函数也将 obj 添加到 [[KeptAlive]] 列表中。
5:不幸的是,根据 this very interesting es-discourse thread.[=137,“什么构成“观察”的概念故意含糊不清” =] 6: 虽然指定不可判定的属性看起来毫无用处,但实际上并非如此。指定一个更差的近似值,例如所说的可达性,将排除 一些 在实践中可能的优化,即使不可能实现通用的 100% 优化器。 dead code elimination.
的情况类似 7: 指定可达性的概念实际上比描述活跃性要复杂得多。请参阅注释 5,它给出了一些结构示例,其中对象可以通过内部槽和规范类型字段访问但应该被垃圾收集 nonetheless.
8:另见 issue 179 in the proposal and the corresponding PR 了解为什么引入对象集。


示例时间!

It is hard to me to recognize how livenesses of several objects may affect each other.

WeakRef-obliviousness,连同活跃度,捕捉[概念]WeakRef 本身不会让对象保持活动状态(注释 1)。这几乎就是 WeakRef 的目的,但无论如何让我们看一个例子:

{
    const o = {};
    const w = new WeakRef(o);
    t = setInterval(() => {
        console.log(`Weak reference was ${w.deref() ? "kept" : "cleared"}.`)
    }, 1000);
}

(您可以在控制台中 运行,然后 force garbage collection,然后 clearInterval(t);

[第二个概念是]活跃度的循环并不意味着对象是活跃的(注 1)。这个有点难展示,但是看这个例子:

{
    const o = {};
    const w = new WeakRef(o);
    setTimeout(() => {
        console.log(w.deref() && w.deref() === o ? "kept" : "cleared")
    }, 1000);
}

在这里,我们清楚地观察到了o的身份。所以它一定是活的?仅当保存 ow 未被清除时,否则 … === o 不会被评估。所以(包含的集合)o 的活跃度取决于它自己,通过循环推理,一个聪明的垃圾收集器实际上允许收集它而不管闭包。

具体来说,如果确定obj's活跃度取决于确定另一个WeakRef引用对象的活跃度,obj2obj2的活跃度不能假设obj's liveness,这就是循环推理(注1)。让我们尝试用两个相互依赖的对象做一个例子:

{
    const a = {}, b = {};
    const wa = new WeakRef(a), wb = new WeakRef(b);
    const lookup = new WeakMap([[a, "b kept"], [b, "a kept"]]);
    setTimeout(() => {
        console.log(wa.deref() ? lookup.get(b) : "a cleared");
        console.log(wb.deref() ? lookup.get(a) : "b cleared");
    }, 1000);
}

WeakMap 主要用作观察两个对象的身份的东西。在这里,如果 a 被保留,那么 wa.deref() 就会 return 它,b 被观察到;如果 b 被保留,那么 wb.deref() 将 return 它, a 被观察到。他们的活跃度是相互依赖的,但是我们一定不能做循环推理。垃圾收集器可以同时清除 wawb,但不能只清除其中一个。

Chrome 目前确实通过闭包检查可达性,因此上面的代码片段不起作用,但我们可以通过在对象之间引入循环依赖来删除这些引用:

{
    const a = {}, b = {};
    a.b = b; b.a = a;
    const wa = new WeakRef(a), wb = new WeakRef(b);
    const lookup = new WeakMap([[a, "b kept"], [b, "a kept"]]);
    t = setInterval(() => {
        console.log(wa.deref() ? lookup.get(wa.deref().b) : "a cleared");
        console.log(wb.deref() ? lookup.get(wb.deref().a) : "b cleared");
    }, 1000);
}

对我来说,注释 2(WeakRef-obliviousness 是在对象集而不是单个对象上定义的,以解释循环。如果它是在单个对象上定义的,那么循环中的对象将是即使它的 Object 值仅通过循环中其他对象的 WeakRefs 观察到,也被认为是实时的。) 似乎说的是完全相同的事情。注释被介绍给 fix the definition of liveness to handle cycles,那个问题还包括一些有趣的例子。