为什么垃圾收集器会冻结执行?

Mar*_*tin 13 language-agnostic garbage-collection

我在回家的路上想着垃圾收集,我开始疑惑,为什么垃圾收集器完全冻结程序的执行?就个人而言,我本来会设计它来阻止任何尝试分配新对象的线程,但是运行的线程将保持不变.我无法想象与垃圾收集器当前的工作方式相比,这会是一个问题.

Jon*_*rop 13

我在回家的路上想着垃圾收集,我开始疑惑,为什么垃圾收集器完全冻结程序的执行?

GC设计中的延迟和吞吐量之间存在折衷.您可以单独处理堆分配的块("增量"),也可以批量处理它们并同时处理它们("停止世界").完全增量收集永远不会完全冻结程序,它具有非常低的延迟,但它的吞吐量也非常低.阻止世界垃圾收集器具有最差的延迟(将程序冻结几秒甚至几分钟)但接近最佳吞吐量.

今天所有主要的生产GC都提供了一个中间层,通常采用分代收集,分批收集每个线程的苗圃世代,并共同或同时收集共享的老一代.因此,只有托儿所收集会产生暂停,并且托儿所的大小是有限的,因此暂停时间保持较低,例如,使用工作站GC在.NET中为10-100ms.

对于一个永不停顿的简单GC算法,请参阅Baker的跑步机.有关垃圾收集的更多信息,我强烈推荐内存管理参考垃圾收集手册.

这里的其他答案中有很多错误的信息.Jon Skeet编写了一些源代码,并从垃圾收集的角度开始讨论它.您需要非常小心这样做,因为源代码与GC看到的内容几乎没有对应关系.编译器执行指令块重新排列,寄存器分配,提升等,所有这些都会影响GC在运行时可见的内容.特别是,源代码中的范围不是通过编译代码来执行的,并且通常用相关的活跃概念替换.乔恩还写道,你必须停下来才能获得全球根源.虽然它是最有效的方式来获得全球根和所产生的停顿几乎总是很小的(亚毫秒),因为你只是复制比堆栈从每个线程KB少并不完全正确.

Powerlord写道,移动收集器必须阻止读取,因此必须阻止所有读取的线程.这也不是真的.最简单的计数器示例是不可变数据:参照透明度意味着您可以安全地从任何副本中读取.

Kico写道,需要暂停才能确定可达性.这也不是真的.请参阅Dijkstra关于"即时"收集器和任何最新实时GC(如Stacatto)的研究.

Jerry Coffin写下了最好的答案,但移动并不是GCs暂停的原因.有些GC不会移动但会暂停(例如HLVM)和那些移动但不停顿的GC(例如Stacatto).


Jon*_*eet 12

现代垃圾收集器(无论如何在.NET和Java中)实际上并不"阻止世界" - 它们会同时收集各种聪明的东西.

但是,您可能想要考虑这样的情况:

 object x = null;
 object y = new object();
 ...
 x = y;
 y = null;
Run Code Online (Sandbox Code Playgroud)

现在,假设GC查看x,然后...运行下面的行,然后GC查看y- 它将不会看到任何活动对象......但对象应该仍然是活动的.

基本上,需要有一定量的暂停才能获得一致的参考集.然后是压缩,参考重新分配等.然而,它并没有像过去那样需要在整个GC循环中停止所有事情.然而,考虑到它确实很痛苦:)

  • @JonHarrop:我想我们必须同意不同意.所以评论并不是这次讨论的正确媒介,而且我从过去与你的讨论中得知,我们必须写下很多东西才能在这里取得任何实际进展. (4认同)
  • @JonHarrop:我并不是建议源和GC之间的直接对应关系.我在谈论GC检查变量的值,然后运行一些代码,然后GC检查另一个变量的值. (3认同)
  • 这是关于HotSpot的GC(有点老了,不可否认):http://www.ibm.com/developerworks/java/library/j-jtp11253/ (2认同)
  • @Qtax:我不知道在任何JVM或.NET实现中.一开始,引用计数在周期周围存在问题. (2认同)
  • @Qtax您已经描述了在这种情况下基于范围的引用计数(例如C++中的智能指针)会做什么,但是大多数GC使用跟踪而不是引用计数,因为跟踪更快并且处理周期.请注意,跟踪和引用计数是彼此的对偶(一个搜索生命,另一个搜索死亡),David Bacon发表了一篇关于此的大论文,称为"垃圾收集的统一理论".http://www.research.ibm.com/people/d/dfb/papers/Bacon04Unified.pdf (2认同)

Pow*_*ord 6

除了Kico Lobo所说的,Garbage Collectors还可以在内存中移动东西.

因此,它们不仅要阻止写入内存的线程,还要阻止从内存中读取的线程.

这是每个线程.