22

这是另一个“请告诉我如何强制 Java 垃圾收集器运行”的问题。在我们的应用程序中,我相信我们有充分的理由这样做。

这是一个服务器应用程序,通常有大约 500 万个活动对象。每 5 分钟一次,我们执行一个大约需要 60 秒的分析任务。如果在分析运行时触发了完整的 GC,则将有大约 40M 的活动对象。分析完成后,额外的 35M 对象变为垃圾。服务器必须始终保持对请求的响应(即使在分析运行时)。

我们发现,如果在分析未运行时调用完整的 GC 大约需要 1.5 秒,但在分析运行时大约需要 15 秒。不幸的是,我们的分配模式使得完整的 GC 通常在分析期间触发,即使分析只运行了 20% 的时间。(每第三或第四次分析运行触发一次完整的 GC。)

如果老一代中的可用空间低于某个阈值(5GB),我在开始分析运行之前添加了代码来调用备受鄙视的 System.gc()。好处是非常可观的:我们获得了 1.5 秒的暂停时间而不是 15 秒的暂停时间,并且我们将更多的垃圾释放到交易中。但是,有时 System.gc() 调用会被忽略,几分钟后我们会在自动触发 GC 时暂停 15 秒。

那么我的问题是:我们可以做些什么来更有力地说服垃圾收集器运行吗?我们正在运行 1.7.0_09-icedtea 并使用 Parallel GC。我想要(a)一种手动强制垃圾收集的可靠方法,或者(b)某种方式来调整收集器,以便它做出更智能的自动决策。(b) 似乎很难,因为我不清楚收集器如何检测到我们的工作集以这种戏剧性的方式变化。

如果需要,我愿意求助于大量黑客;这对我们来说是一个严重的问题。(我们可能会考虑使用 CMS 或 G1 压缩器作为替代方案,但我对 CMS 对吞吐量的影响持怀疑态度,而且 G1 被认为在我们使用的大字节数组中表现不佳。)

附录:在生产中,到目前为止,我们的经验是 System.gc()通常确实会触发完整的垃圾回收;至少,在我们调用它的情况下。(我们只每 10 到 30 分钟调用一次,堆中有些但没有完全填满垃圾。)能够更可靠地触发垃圾收集会很好,但它在大多数情况下都对我们有帮助。

4

4 回答 4

6

您的问题是您在同一个 JVM 中运行两个具有完全不同要求和内存配置文件的应用程序。

在非面向用户的过程中单独运行数据分析,以便面向用户的服务器保持持续响应。我假设定期分析会生成某种摘要或结果数据;通过将其传送到面向用户的服务器,使其可供最终用户使用,以便可以从那里提供服务,或者让您的前端与分析服务器分开获取它。

于 2013-10-17T01:05:56.653 回答
2

考虑使用非托管内存,即ByteBuffers 代替字节数组。

我只能提供一个 hack,它需要一些调整,然后可能会或可能不会工作。我会先尝试更理智的解决方案。当你想强制 GC 时,通过分配大量内存来实现。这样做是为了可以立即回收内存,但不能优化整个分配(sum += new byte[123456].hashCode()应该做的事情)。您需要找到一种可靠的方法来确定何时停止。带有终结器的对象可能会告诉您,或者观看runtime.getFreeMemory可能会有所帮助。

于 2013-10-17T01:21:58.103 回答
1

我不想直接回答您的问题(我不能),而是想提供一个可能的替代方案。

听起来您在分析运行期间分配了大量的大字节数组,然后在运行结束时允许它们被垃圾收集(或试图在下一次运行之前强制它们被垃圾收集)。

相反,如果可能,请尝试管理您自己的字节数组池,以便在最好的情况下,在应用程序首次启动时分配所有需要的数组,然后它们在应用程序的生命周期中存在,并且不要不需要垃圾收集。

当然,这个想法可以扩展到更复杂的数据结构和对象实例。

这比仅仅在需要时分配内存并在不需要时“释放”它要多得多,但应该大大减少垃圾收集器需要做的工作。

于 2013-10-17T00:57:16.853 回答
-1

我发现 java GC 处理大量对象(20-100m 个对象)非常糟糕。如果这些对象实际上还活着,您的情况会更糟,因为即使没有任何东西可以实际收集,GC 也会很糟糕。

解决方案是减少对象的数量(而不是您正在使用的总内存)。我敢猜测您的分析阶段正在使用集合和许多原始包装器(整数、长整数等)。如果是这种情况,一种解决方案是切换到原始集合库。我创建了一个这样的库来解决我遇到的类似问题,我在很长时间内使用 100m 活动对象运行模拟。该库称为Banana,有关详细信息,请参阅 wiki。

于 2013-10-17T16:17:44.053 回答