什么会触发第二代垃圾回收?

23

我有一个奇怪的情况需要解决。

起因:

我在一台物理机器上运行程序,该机器有16个核心和128GB的RAM。我试图确定为什么它没有使用所有可用的核心,通常平均使用20-25%的CPU(即16个核心中的4-5个)。当我查看性能计数器时,它们显示大约60-70%的时间在垃圾收集中。

作为参考,我正在使用.NET Framework 4和TPL(Parallel.ForEach)来线程化我的程序的性能密集部分。我将线程数限制为核心数。

问题:

我正在创建大量对象,远远超过垃圾收集器能够有效处理的数量,因此它花费了大量时间在垃圾收集器中。

到目前为止的简单解决方案:

我正在引入对象池以减轻垃圾收集器的压力。我将继续对对象进行池化以改善性能,已经对一些对象进行了池化,将垃圾收集从60-70%的时间降至45%的时间,并且我的程序运行速度提高了40%。

令人不满的问题(希望您能为我回答):

运行时,我的程序最多使用14GB可用RAM,相对于128GB的RAM来说很小。除此之外,没有其他东西在这台机器上运行(它纯粹是我的测试平台),而且有足够的RAM可用。

  • 如果有足够的RAM可用,为什么还会出现任何gen2(或full)集合?许多这些gen2集合(数千个)正在发生。也就是说,它如何确定开始进行gen2集合的阈值?
  • 为什么垃圾收集器不会简单地延迟任何完整的收集,直到物理RAM的压力达到更高的阈值?
  • 有没有办法配置垃圾收集器等待更高的阈值?(即,如果不需要,则根本不进行收集)

编辑:

我已经使用了使用服务器垃圾收集器的选项... 我需要知道的是是什么触发了gen2收集,而不是服务器垃圾收集器更好(我已经知道了)。

2个回答

21

据我记得,客户端垃圾回收是默认选项。我的经验是,在堆积集之前不会让堆积变得非常大。对于我的重型处理应用程序,我使用“服务器”垃圾回收。

您可以在应用程序配置文件中启用服务器垃圾回收:

<?xml version ="1.0"?>
<configuration>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

这对我的性能产生了巨大的影响。例如,我的一个程序在垃圾回收方面耗费了超过80%的时间。启用服务器GC将其降至仅略高于10%。由于GC让它运行,内存使用量增加了,但对于我大多数应用程序来说这没问题。

还有一个会导致Gen 2收集的因素是大对象堆(LOH)。请参见CLR Inside Out:Large Object Heap Uncovered。简而言之,如果超过了LOH阈值,它就会触发Gen 2收集。如果您正在分配大量短生命周期的大对象(约85千字节),那么这将是一个问题。


我已经在使用服务器垃圾收集器(由于每个核心规则有一个堆,因此在许多核心上更具性能)。虽然我感谢您的建议,但那并没有真正回答我的问题:( - Jeffrey Cameron
这是一个很好的观点。虽然我不认为我正在这样做,但我的LOH堆通常只有一个数组,但我会进一步深入检查一下。 - Jeffrey Cameron
6
哇!显然我非常愚蠢。在我的程序中我写成了<gcserver enabled="true">而不是<gcServer enabled="true">。很明显这是区分大小写的!!>-( 我做出了更改,并且看到性能几乎提高了10倍。在平均情况下,%GC时间从45%降至12.5%。我仍会回去继续池化对象以挤出更多性能,但如果没有你的评论,我永远不会想到回去检查垃圾收集器是否被设置为服务器模式。再次非常感谢! - Jeffrey Cameron
@Jeffrey:我希望我能说我从来没有这样做过。我不认为我曾经在gcServer上这样做过,但我以前在配置文件中遇到了大小写敏感的问题。很高兴你解决了它。 - Jim Mischel

10
从模糊的记忆和阅读中得知:http://msdn.microsoft.com/en-us/library/ee787088.aspx,我认为Gen 2 GC的一个触发器可以是Gen 2段填满。文章指出,服务器GC使用更大的段,因此如前所述,这对性能非常重要。
让机器等待直到几乎没有可用内存将意味着在某个阶段你会遇到一次非常严重的GC。这可能不是理想的情况。如果您的GC时间很长,这表明您正在分配太多的对象,这些对象足以经过gen 0和1,并以重复的方式进行。如果应用程序的内存使用量不会随时间而增加,则表明这些对象实际上是短暂的,但足以幸存于0和1的收集中。这是一个糟糕的情况-您正在分配一个短寿命的对象,但要付出完全清除它的Gen 2成本。
如果是这种情况,您可以采取以下几个方向:
1. 尝试使短寿命对象更早地可回收(因此它们不会进入gen 2,因此GC成本更低) 2. 尝试分配较少的短寿命对象(因此GC发生的频率较低,您有更多时间在分配强制进行GC并将对象移动到较旧代之前完成使用短寿命对象) 3. 对于短期使用的对象,请使用堆栈分配的值类型而不是引用类型(如果符合您的目的) 4. 如果您知道需要大量这些对象,请提前将它们汇集。听起来您正在这样做,但仍然必须有很多分配才能使GC达到45%。如果您的池不够大,请提前分配更多-正如您所说,您有足够的闲置内存。
这些方案的结合可能是一个好的解决方案。您需要充分了解您正在分配哪些对象,它们的寿命以及它们实际上需要多长时间才能实现您的目的。
GC对具有短寿命(即可以被GC快速回收)或具有长寿命的长期/永久对象感到满意。在这两个类别中分配大量对象是会带来痛苦的。因此,请分配较少的对象或将其寿命更改为匹配其使用场景。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接