尝试协助JavaScript垃圾回收器是否有意义?

20

随着我更频繁地使用JavaScript作为高级面向对象语言,当我完成对象后,我发现自己像C / C ++程序员一样思考。 我知道GC最终会运行并清理我的代码,但我是否可以做些事情来帮助它呢?

例如,我有一个由大型/复杂的主对象数组组成...每个主对象内部可能有数组和其他附属对象引用。 如果我完成了一个主对象并仅从数组中删除它,GC将最终自行找出该对象指向的所有其他内容,包括循环内部引用等。 但是,在从存储数组中删除主对象时,是否有意义通过遍历数组,并将array.length = 0作为任何数组和reference=null的对象引用来使GC的工作更加轻松(例如,显式删除引用意味着GC要跟踪的内容更少)? 这种情况类似于手动析构函数。 做这件事是否值得,还是说我在浪费时间/精力而没有收益?

我想这更多是关于GC理论的问题(Java等),但我主要对JavaScript感兴趣,目的是回答这个问题。

谢谢!


3
我简直不敢相信有人提出了一个有趣/有用/不愚蠢的问题。点赞。+1。 - user529758
我觉得这个答案非常有趣:https://dev59.com/42855IYBdhLWcg3weEKJ#4324162 - Hanlet Escaño
3个回答

6
这可能取决于具体的垃圾收集器,所以答案会根据您使用的JavaScript引擎而有所不同。
首先,我要注意到最好的应用程序代码要做两件事情。它要实现其功能和性能的技术目标,并且要对变化具有弹性。额外的代码应该有足够的目的来证明增加复杂性是合理的。
话虽如此,根据Google关于V8的说明,这是Chrome使用的JavaScript引擎,
V8通过垃圾回收来释放不再需要的对象所占用的内存。为了确保快速对象分配、短暂的垃圾回收暂停和无内存碎片化,V8采用了一种称为“停止-世界、分代、准确”的垃圾收集器。这意味着V8具有以下特点:
- 在执行垃圾回收循环时,会停止程序的执行。 - 在大多数垃圾回收循环中,只处理对象堆的一部分,以最小化应用程序停止的影响。 - 始终准确地知道所有对象和指针在内存中的位置,避免错误地将对象识别为指针,从而导致内存泄漏。
在V8中,对象堆被分为两个部分:新空间用于创建对象,旧空间用于存放在垃圾回收循环中幸存下来的对象。如果在垃圾回收循环中移动了一个对象,V8会更新所有指向该对象的指针。
分代垃圾收集器通常会在堆之间移动对象,将所有活动对象移动到目标堆中。未移动到目标堆的任何对象都被视为垃圾。目前尚不清楚V8垃圾收集器如何识别活动对象,但我们可以参考其他垃圾收集器的实现来获取一些线索。
以Java的并发标记-清除收集器为例,它是一个有着良好文档记录的垃圾收集器的行为示例。
  1. 停止应用程序。
  2. 构建从应用程序代码可达的对象列表。
  3. 恢复应用程序。与此同时,CMS收集器运行“标记”阶段,将可达对象标记为“非垃圾”。由于这与程序执行并行进行,它还跟踪应用程序所做的引用更改。
  4. 停止应用程序。
  5. 运行第二个(“重新标记”)阶段来标记新可达对象。
  6. 恢复应用程序。与此同时,它“清除”所有被标识为垃圾的对象并回收堆块。

基本上是一个图遍历,从一组特定节点开始。由于无法访问断开连接的对象,它们与其他断开连接的对象之间的连接性不会起作用。

关于Java垃圾回收工作原理有一份很好但有些过时的白皮书,可以在http://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf找到。垃圾回收并不仅限于Java,因此我认为Java虚拟机和其他运行时(如JavaScript引擎)采用的各种方法可能存在一些相似之处。

陈纳德写了一篇博客文章,指出释放即将释放的内存对性能会产生负面影响。这个上下文是关于在应用程序关闭时手动释放内存。由于块可能已经被交换到磁盘上,遍历引用的行为可能导致这些块被交换回来。在这种情况下,程序正在遍历那些刚刚被标记为可用并且未被触碰的块。

因此,在操作系统可能已经交换出一些块的情况下,“辅助”垃圾收集器,特别是对于存活时间较长的对象,可能会减慢速度。

而如果你生成的数据不足以引起交换的关注,那么一个合理的垃圾收集器不会花费足够长的时间来注意到这一点。

因此,努力可能是不值得的,而且可能适得其反。尽管删除对堆“dominators”(支配者)的引用可能是有道理的。这些对象,如果被收集,将允许收集许多其他对象。因此,删除对集合本身的引用,但不删除对集合中每个项目的引用。


1
有趣,谢谢。当然,这取决于具体的实现方式,但这部分可能是一个关键点:“由于无法访问断开连接的对象,它们与其他断开连接的对象的连接性不应发挥作用”。 - mark
如果没有任何引用将存活于对象图的任何部分,那么遍历该图以打破内部引用将是徒劳无功的。在大部分引用图不再有用但某些较小部分仍可能有用且可能具有针对它们的引用的情况下,销毁对图的无用部分的引用可能是防止内存使用不断增长的关键。 - supercat
同意,在某些情况下,即使您不希望父对象有资格进行收集,当您知道它们不再需要时,丢弃单个引用仍然是有意义的。 - GargantuChet

5
很少。
这与从不不同。通常情况下,如果您以合理的方式开发,未使用的对象将适当地超出范围,垃圾回收器将按预期工作。除非您真正了解自己在做什么,否则帮助垃圾回收器完成其工作的努力通常不会起到预期的作用。垃圾回收是各种Javascript引擎创建者进行了重大优化的领域。通常情况下,除非:
  • 您已经通过分析工具的硬数据证明存在实际需求,并且
  • 您可以展示真正理解有关如何更改才能帮助垃圾回收器的知识。
这两个条件都很难达成。因此,也许最好的答案是,除非您已经足够先进,能够理解异常情况,否则您可能不应该担心这个问题。

3
非常聪明的人已经在垃圾回收模型上努力工作了。你所做的任何事情要想大幅提高性能的可能性相对较低。
如果垃圾回收成本正在成为你的主要问题,你最好专注于减少需要进行垃圾回收的对象数量(考虑 Object Pool Pattern)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接