垃圾回收和线程

AFAIK 当 GC 执行其操作时,VM 会阻止所有正在运行的线程,或者至少在压缩堆时会阻止它。在CLR和JVM的现代实现中是否如此(截至2010年1月的生产版本)?请不要提供GC的基本链接,因为我了解基本的工作原理。

我假设全局锁定是这种情况,因为当发生压缩时,引用在移动期间可能是无效的,并且似乎最简单的方法是锁定整个堆(即,通过阻塞所有线程间接)。我可以想象更强大的机制,但KISS经常占上风。

如果我不正确,我的问题将通过对用于最小化阻止的策略的简单解释来回答。如果我的假设是正确的,请就以下两个问题提供一些见解:

  1. 如果这确实是行为,那么像JBOSS和Glassfish这样的重量级企业引擎如何保持持续的高TPS率?我在JBOSS上做了一些谷歌搜索,我期望在APACHE上找到一些适合Web处理的内存分配器。

  2. 面对 NUMA 式架构(可能在不久的将来),这听起来像是一场灾难,除非进程受线程和内存分配的 CPU 限制。


答案 1

答案是,这取决于所使用的垃圾回收算法。在某些情况下,您是正确的,所有线程都在 GC 期间停止。在其他情况下,您是不正确的,因为垃圾回收在正常线程运行时继续进行。要了解GC如何实现这一目标,您需要详细了解垃圾收集器的理论和术语,并了解特定的收集器。它根本不适合简单的解释。

哦,是的,值得指出的是,许多现代收藏家本身并没有压实阶段。相反,它们的工作原理是将活动对象复制到新的“空间”,并在完成后将旧的“空间”归零。

如果我不正确,我的问题将通过对用于最小化阻止的策略的简单解释来回答。

如果你真的想了解垃圾回收器是如何工作的,我建议:

...请注意,找到生产垃圾回收器内部的准确,详细,公开的描述并不容易。(尽管在热点GC的情况下,您可以查看源代码...)

编辑:回应OP的评论...

“似乎就像我所想的那样 - 没有绕过'停止世界'的部分。

这要视情况而定。对于 Java 6 并发收集器,在标记根(包括堆栈)期间有两个暂停,然后并行进行其他对象的标记/复制。对于其他类型的并发收集器,在收集器运行时使用读取或写入屏障来捕获收集器和应用程序线程会相互干扰的情况。我现在在这里没有我的副本[琼斯],但我也记得,有可能使“停止世界”的间隔可以忽略不计......代价是更昂贵的指针操作和/或不收集所有垃圾。


答案 2

您是对的,垃圾回收器将不得不暂停所有应用程序线程。这个暂停时间可以通过使用 sun JVM 重新计算,并使用并发收集器来重新计算,该收集器在不停止应用程序的情况下完成一些工作,但它必须暂停应用程序线程。

有关 sun JVM 如何在最新的 JVM 中管理垃圾回收的详细信息,请参阅此处 http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html#par_gc 和此处 http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html#cms

对于Web应用程序,我不认为这是一个问题。由于用户请求应在很短的时间内完成,<1s,因此分配给服务请求的任何临时对象都不应退出年轻一代(前提是其大小合适),因为它们被非常有效地清理。其他生命周期较长的数据(如用户会话)将停留更长时间,并可能影响在主要 GC 事件上花费的时间。

在高 TPS 应用程序上,一种常见的策略是使用会话关联和负载 ballancing 在相同或单独的硬件上运行应用程序服务器的多个实例。通过这样做,每个JVM的单个堆大小保持较小,从而减少了执行主要收集时GC的暂停时间。通常,数据库成为瓶颈,而不是应用程序或JVM。

在 J2EE 中,最接近 Web 特定内存分配器概念的是框架和应用程序服务器执行的对象/实例池。例如,在 JBOSS 中,您有 EJB 池和数据库连接池。但是,这些对象通常是池化的,因为它们的创建成本很高,而不是垃圾回收开销。