服务器模式 GC 似乎从不收集 Gen 0 堆

Server mode GC seems to never collect Gen 0 Heap

澄清问题 (tl;dr)

在阅读和分析了下面涵盖的所有结果后,问题似乎归结为 GC 在服务器模式下没有为我们的应用程序收集 Gen 0 堆,一旦它更改为工作站模式,问题就消失了.

原始问题和详细信息

我的问题有点相关:This question and this question

我们最近 运行 发现我们的测试环境中的 .NET 应用程序似乎存在内存泄漏问题,工作进程的使用量会在负载下迅速攀升至 450MB 左右,或者在无负载下逐渐攀升至 450MB 左右.

问题无法在我们的开发环境中复制,主要区别在于开发环境是物理服务器,而测试环境是虚拟化的并由 Puppet 控制(除此之外我对环境了解不多他们自己)。

为了希望看到哪些对象占用了所有内存,我在测试服务器上 运行 Ants Memory Profiler,我发现所有内存都处于未使用状态并且从未被释放.

在研究可能导致此问题的原因时,我遇到了 this forum post which in turn lead me to this article

我最终尝试了它推荐的配置,将 GC 置于工作站模式:

<configuration>
  <runtime>
    <gcServer enabled="false"/>
    <gcConcurrent enabled="false"/>
  </runtime>
</configuration>

在 运行 iisreset 和重新 运行 我的内存分析之后,问题完全消失了,这很好,但仍然不能真正解释最初发生的事情。

我确实做了更多阅读并发现 ,这使我相信此配置更改最终可能会损害我们应用程序的吞吐量。

所以我的问题是:什么会导致 IIS 工作进程积累大量从未被垃圾回收的未使用内存?

编辑: 为了进一步澄清我的问题,我相信我们已经证明代码对此不负责任,因为完全相同的代码在开发环境中不会遇到此问题。

这是我在配置更改前后对内存分析截取的屏幕截图,这里没有太多信息,但图表确实很好地显示了内存趋势。

编辑 2: 这是我能收集到的服务器规格,我可能会得到更多,只是需要时间。

开发环境: 实体机 CPU:单核 内存:6GB

测试环境: 虚拟机 CPU:4 个逻辑线程(我无法评论 CPU 计数) 内存:8GB

Machine.Config 文件的唯一区别是开发环境正在添加 "Microsoft.VisualStudio.Diagnostics.ServiceModelSink.Behavior" 端点和服务行为。

并且测试环境目前有之前在aspnet.config文件中提到的GC设置。

编辑 3: 做了一些更多的分析并注意到我可以在 Ants 中添加更多的计数器,特别是我添加了 "Gen 0 heap size" 看起来这就是问题的根源。当 GC 处于服务器模式时,当我触发我用于分析的测试时,这条线立即跳到 ~300MB,然后回落到 ~230MB,但永远不会一直回落(下图)。

运行 在工作站模式下使用 GC 进行相同的分析可以看到 Gen 0 堆大小的初始峰值要小得多,并且 return 在请求完成后基本上回到零(下图) .

对此进行更多搜索让我找到了 another 更多相关的 SO 问题,但是他的发现是这种内存使用不是问题,而在我的情况下,服务实际上需要手动重新启动每天至少一次。

我还发现 this article 对这个问题有以下说法(这似乎几乎完美地描述了正在发生的事情:

Generation 0 is likely to have a larger number of objects on a 64-bit system, especially when you use server garbage collection instead of workstation garbage collection. This is because the threshold to trigger a generation 0 garbage collection is higher in these environments, and generation 0 collections can get much bigger. Performance is improved when an application allocates more memory before a garbage collection is triggered.

虽然问题仍然存在,但在服务器模式下,第 0 代堆似乎从未被收集,而不是经常收集。

不是直接的答案,更像是一个创可贴,但是如果你可以 运行 .Net 4.5.1 那个 w3wp 进程中的代码,你可以压缩 LOH 和大量未使用的分配内存 [可能]减少。

您可以创建 App Start 代码来启动一个计时器,该计时器 运行 经常从 w3wp.exe 进程内部执行此操作。

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;

GC.Collect(); 

虽然直到 4.5.1 才添加此功能,因此您不能在未针对框架的至少 4.5.1 的 .Net 程序集中使用它。

这可能会让您摆脱所做的 web.config 更改,并防止未分配的内存在不需要时保持高位。

经过大量研究、阅读和分析,我已经能够证明我们的 IIS 内存使用实际上在标准范围内;这是使用 SysInternals Test Limit 实用程序将服务器的物理内存使用率推到接近最大值来完成的,一旦完成,我们所有的应用程序都会释放它们的内存。

我们的测试环境中仍然存在某种内存问题,我需要对其进行调查,但此时我想我可以自信地说这完全无关。

这个故事的寓意是不要假设报告的问题原因是正确的。

您可以尝试在.NET Framework目录下的Aspnet.config文件中启用gcTrimCommitOnLowMemory设置:

When the gcTrimCommitOnLowMemory setting is enabled, the garbage collector evaluates the system memory load and enters a trimming mode when the load reaches 90%. It maintains the trimming mode until the load drops under 85%.

https://msdn.microsoft.com/en-us/library/bb384209(v=vs.110).aspx

另一个选项(自 .net v4.5 起)是将 performanceScenario 设置为 "HighDensityWebHosting" 22=]Aspnet.config 文件。这对于共享托管方案很有用,因为它将 "tune garbage collection to optimize for memory": http://www.asp.net/aspnet/overview/aspnet-and-visual-studio-2012/whats-new#_Toc_perf_5

正如您从 CoreCLR 来源中看到的那样,HighDensityWebHosting 选项主要禁用 gcServergcConcurrent设置,但启用 gcTrimCommitOnLowMemoryhttps://github.com/dotnet/coreclr/blob/cbf46fb0b6a0b209ed1caf4a680910b383e68cba/src/vm/perfdefaults.cpp