@
FaiChou 这个其实很难说是一个 bug。“a 在第五行被回收了”也完全没有保证。
另外 GC 这个东西很难被定义,我至今没见过哪个语言成功地在自己的 spec 中把 GC 以确实有用的形式定义出来。
https://en.wikipedia.org/wiki/Tracing_garbage_collection#Determinism:Tracing garbage collection is not deterministic in the timing of object finalization. An object which becomes eligible for garbage collection will usually be cleaned up eventually, but there is no guarantee when (or **even if**) that will happen.
意思是就算一个对象实际不被引用,Tracing GC 依然可以**根本不回收**它
这个不仅仅是 wiki 这么说,Scheme 的 spec 更有意思:
www.r6rs.org/final/r6rs.pdf 第一页就说了:No Scheme object is ever destroyed. The reason that implementations of Scheme do not (usually!) run out of storage is that they are **permitted** to reclaim the storage occupied by an object if they can prove that the object cannot possibly matter to any future computation ... "Permitted" 意思是 Scheme 实现可以根本没有 GC ...(关于 Scheme 对内存这个东西的定义后面还有更多的描述)
JLS 写得很模糊:When an object is no longer referenced, it **may** be reclaimed by the garbage collector. 但是 JLS 里面还隐藏了另外一个有趣的东西,就是 finalizer,紧接着上面一句话:“If an object declares a finalizer, the finalizer **is** executed before the object is reclaimed to give the object a last chance to clean up resources that would not otherwise be released.”,后面还有一节专门讲 finalizer 的同样说:"Before the storage for an object is reclaimed by the garbage collector, the Java Virtual Machine **will** invoke the finalizer of that object.",但是后面又说“A finalizable object has never had its finalizer automatically invoked, but the Java Virtual Machine **may** **eventually** automatically invoke its finalizer.”
这个定义就算是把 may 去掉了也没用 ... 比如考虑我定义成对象 eventually 会被回收,但是同时实现成“无限长”之后会被回收,在实际情况中依然是一个非常受限的实现
这就相当于根本就没有办法从 spec 的角度 enforce 任何 GC 的行为。而我们一般意义上研究的 GC,从这个角度来说都属于编译器的“优化”,“优化”就是 spec 没有强制要求(实际上正常的 spec 不会有任何相关内容),而实现所添加的在保持 standard conformance 的同时为了增强某些方面性能做得的额外的 feature。这就是说为什么这个 Chromium 的 issue 是个 feature 不是个 bug。
有一个很有意思的例子就是 PTC (proper tail call),同样是 R6RS 的要求:A Scheme implementation is properly tail-recursive if it supports an unbounded number of active tail calls.(在内存受限的前提下,这个其实等价于 tail call 的 space complexity 是 O(1) 的)
有意思的是,C/C++ 标准里至今没有这样的内容,但是主流的编译器都做了这样的优化。ES6 做了类似的要求(不过标准的表达有些区别),但是好像现在只有 JavaScriptCore 的实现是可用的?
当 PTC 这条不在标准里面的时候,它就属于编译器的一个优化,优化不仅仅是实现相关的,更是没有保证的,编译器可以选择做这个优化,也可以选择不做这个优化。C/C++ 标准没有 PTC 是一件十分细思恐极的事情——背后的 implication 是:我无法使用标准 C/C++ 的函数调用实现一个符合 Scheme 标准的解释器( ES6 同理),我必须自己维护函数调用的 activation record (或者依赖于特定的实现提供了这样的优化的这个前提 (这就超出标准 C/C++ 的范畴了)),这会让实现 DSL 更麻烦。而如果使用 ES6 来写,在实现符合标准的前提下就没有这个问题,而至于 V8 不符合 ES6 标准,就是另外一个问题了 ...
需要注意的是虽然 PTC 有若干其实挺 well-known 的实现方式,但是标准(尤其是 Scheme 标准)并没有规定该怎样实现 PTC,它仅仅规定实现应该满足的一个 性质。和其他的优化一样,就算标准里面没有这个东西,实现者也可以选择做这样一个优化(确实也很有用),但是这东西最后能进到标准里面,前提之一是这个行为可以清晰地被定义。在实现这一行为的过程中,某些实现可能会让 tail call 在时间上更慢,某些实现则会在保证 PTC 的同时试图让 tail call 的性能最优,但是这些都不在标准规定的范围内。