2

我的应用程序在具有 16 个处理器和 64 GB 内存的服务器上正确运行。我有多个进程,我尝试将我的进程的最大堆限制为 8 GB。

我的问题是我有某种形式的生产者-消费者模式,我必须限制生产速率,否则我会耗尽内存,因为老年代的垃圾收集发生得太少了。

  • 当我监控我的应用程序时,我可以看到运行 4 小时后,我在 ParNEW 中花费了 7 分钟,在 ConcurrentMarkSweep 中花费了 0.775 秒。
  • 我的堆占用上升到大约 6GB,然后下降到 1GB,然后慢慢开始上升到 6GB,然后再次下降。周期约为 10 分钟

通过 JVisualVM 我可以看到 90% 的内存占用是由 CMS Old Gen 给定的,如何强制并发标记扫描运行得更频繁一些?


@PeterLawrey 评论非常相关,因为我的应用程序运行在应用程序服务器的顶部,该应用程序服务器专为事件驱动处理和数据分区而设计,例如 Terracotta 或 Coherence。底层实现很可能包括用于事件处理的排队系统。

我的问题是限制堆大小不是解决方案,因为正如我所经历的那样,应用程序没有更频繁地进行垃圾收集,而是耗尽了内存。

4

2 回答 2

3

我必须限制生产速度,否则我会耗尽内存

如果您使用无界队列,就会发生这种情况。如果你的生产者超过了消费者的速率,队列可以无限增长,直到你的内存用完。限制生产者可以确保队列保持在合理的大小。

解决此问题的一种简单方法是在队列太长时延迟生产者。

private final BlockingQueue<Task> tasks = new ArrayBlockingQueue<Task>(1000);

// the producer slows when the queue length gets too long.
tasks.offer(newTask, 1, TimeUnit.HOURS);

这将确保队列永远不会太长,但不会不必要地减慢生产者的速度。

BTW:与链接队列相比,使用 ArrayBlockingQueue 还可以减少产生的垃圾量。

如果你真的需要一个无界队列,你可以使用我写的一个库,但它的级别相对较低。Java Chronicle生产者可以超过消费者的主内存大小。它仅受磁盘空间大小的限制。

于 2012-09-17T15:00:38.117 回答
2

我认为通过设置较低的初始占用率,您可以触发频繁的收集。

-XX:CMSInitiatingOccupancyFraction=<nn>
于 2012-09-17T14:51:51.693 回答