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

Meh*_*lik 4 java multithreading garbage-collection

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

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

the*_*472 5

原则上他们没有必要.但是编写和使用完全并发的垃圾收集器是

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

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

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

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


Dim*_*rov 5

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

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

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

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

也有像 G1GC 这样的非分代收集器,它们可以显示出很棒的结果(以至于 G1GC 将成为 HotSpot 中的默认收集器),但它们仍然不能保证不会有 stop-the-world 暂停。这里的问题又是并发压缩,但特别是对于 G1,这不是一个问题,因为它可以同时执行一些基于区域的压缩。

说这是刮表面将是一种点缀 - 该区域是巨大的,我建议您阅读一些有关该主题的可访问材料,例如 Gil Tene 的理解垃圾收集了解其背后的一些理论或 Emad Benjamin 的虚拟化和针对一些实际问题和解决方案调整大型 JVM

(*) 不过,这并不是一个完全无法解决的问题。Azul 的 Zing JVM 及其 C4 垃圾收集器声称完全并发收集(有关于它的白皮书,但您可能会发现此处详细信息更有趣)。OpenJDK 的 Shenandoah 项目也显示出非常有希望的结果。尽管如此,正如8472所解释的,您在吞吐量和复杂性方面付出了一些代价。G1GC 团队考虑采用完全并发的算法,但决定 STW 收集器的好处超过 G1GC 上下文中的好处。