为什么垃圾收集器在回收内存之前会停止所有线程

时间:2016-04-19 05:21:20

标签: java multithreading garbage-collection

我正在阅读一些与性能相关的帖子,然后我遇到了句子“ Java的垃圾收集器在回收内存之前停止所有线程,这也是一个性能问题”。我试图在Google上找到它,但我做不到。

有人可以分享一些东西,以便我能清楚这一点吗?

2 个答案:

答案 0 :(得分:5)

原则上他们不必这样做。但是编写和使用完全并发的垃圾收集器是

  • 非常复杂
  • 需要更多的喘息空间才能有效运作,这在内存受限的设备上可能是不可接受的
  • 会导致显着的性能开销。您最终交易吞吐量(在mutators中花费的CPU周期与在收集器线程中花费的cpu周期)以改善(零)暂停时间。对于提供交互式服务的大型堆和多核机器而言,这可能是可接受的交易,但在小批量处理的小型设备或服务上可能是不可接受的。

相反,使用停止世界暂停的实现在CPU利用率方面更简单,更有效,并且存在暂停的缺点。

此外,如果我们将人类作为衡量标准,你必须考虑小堆上的暂停时间相当低。因此,如果您的系统具有比人类更大的容差或更大的容量,那么低暂停/无暂停收集器通常是值得的,这在历史上是罕见的,并且最近随着计算机的不断增长而变得更加普遍。

为避免潜入细节,请考虑参考计数而不是标记扫描紧凑型收集器。完全并发引用计数会导致原子内存访问的开销,以及可能更复杂的计数方案,内存占用更多​​。

答案 1 :(得分:4)

简短而且信息量不大的答案是因为很难不,所以让我们详细说明。

HotSpot JVM中有许多内置收集器(请参阅https://blogs.oracle.com/jonthecollector/entry/our_collectors)。世代收藏家已经发展得很明显,但正如我们所说,他们仍然无法实现完全并发。它们可以同时标记哪些对象已经死亡,哪些不是,它们可以同时扫描死对象,但是它们仍然无法在不停止程序的情况下同时压缩碎片化的生物对象(*)。

这主要是因为通过更改对象的堆位置并更新对它的所有引用来确保您不会破坏某人的程序真的非常非常困难,而不会停止这个世界并一次性完成所有操作。还很难确保你所移动的所有生物都不会在你的鼻子下面改变。

世代收藏家仍然可以运行大量时间而不会停止世界并做必要的工作,但他们的算法仍在推迟不可避免的,不能保证完全并发的GC。请注意在描述许多GC算法时,如何使用大多数并发(即并非总是并发)这样的短语。

还有像G1GC这样的非世代收藏家,他们可以展示出令人敬畏的结果(到目前为止G1GC将成为HotSpot的默认收藏家),但他们仍然不能保证不会有世界停留的停顿。这里的问题同样是并发压缩,但特别是对于G1来说这不是一个问题,因为它可以同时执行一些基于区域的压缩。

要说这是刮表面将是一个点缀 - 该区域是巨大的,我建议你回顾一些关于这个主题的可访问材料,如Gil Tene的Understanding Garbage Collection背后的一些理论或Emad Benjamin的Virtualizing and Tuning Large Scale JVMs提出了一些实际问题和解决方案。

(*)但这不是一个完全无法解决的问题。 Azul的Zing JVM及其C4垃圾收集器声称完全并发收集(有关于它的白皮书,但你可能会发现details here更有趣)。 OpenJDK的Shenandoah项目也是shows very promising results。尽管如此,正如 the8472 所解释的那样,您需要支付一定的吞吐量和复杂的价格。 G1GC团队考虑采用完全并发的算法,但认为STW收集器的好处超过了G1GC的好处。