太多的垃圾回收了

时间:2018-06-29 08:15:00

标签: .net azure garbage-collection

我们的Web应用程序有数百个用户,并且(一如既往)主要由旧代码组成。

迁移到Azure后,我们可以查看和衡量比以前更多的指标。我们遇到了性能问题,我看到我们的垃圾收集正在不断进行(如“性能计数器”部分下的Web应用程序的“诊断”标签中所测量)。一分钟之内,我们就能得到这些数字:

  • 90160 GEN 0垃圾收集
  • 76910第1代垃圾回收
  • 75110 GEN 2垃圾收集

这仅用于18580个HTTP请求,因此平均而言,我们有:

    每个请求
  • 4,85个GEN 0垃圾回收
  • 每个请求
  • 4,13个GEN 1垃圾收集
  • 每个请求
  • 4,04 GEN 2垃圾收集

即使请求数量保持不变(参见图表),这些数字仍在增加

GC going bonkers

我的问题/评论是:

  • 这些数字是GC清理的对象数量还是GC必须处于活动状态的次数?
  • 在这样的负载下,多少GC将被视为“正常”,非常清楚没有一个是完美的答案,但实际上...
  • 即使请求的数量保持不变,GC收集的数量如何仍会像这样增长?

非常感谢您, 约翰


更新1:30/06/2018 @ 8:16 UTC + 2

在更新应用程序见解以更紧密地监视垃圾收集之后,我发现性能受到了极大的欢迎。首先,这是在GC中花费的平均时间百分比:

Average time in GC around 4 and a half percent

平均大约有4.5%的时间(但在此期间夜间处于非活动状态),平均大约有10%的时间偷看。然后我想可视化应用程序处于GC模式的最长时间,而我几乎掉下了椅子:

Maximum amount of time in GC around 99 percent

这可能是错误的图像。但这说明我们的代码必须等待很多时间!我们确实必须解决此问题。

3 个答案:

答案 0 :(得分:2)

对于典型的网络应用程序,这些数字非常高。我会说它们是正常值的10-1000倍(一分钟内出现75110 GEN2。听起来更像是GC的微基准测试:))。

有人打GC.Collect()吗? Grep的源代码。

仍然,您需要找出它们是否引起性能问题。如果他们没有造成此问题,则无需修复此问题。查看在GC计数器中花费的时间。您可以使用PerfView轻松测量已执行的GC暂停。这样您就可以了解客户面临的暂停延迟。

  

这些数字是GC清理的对象数量还是GC必须处于活动状态的次数?

这些是GC,而不是对象。

  

在这样的负载下,多少GC将被视为“正常”,非常清楚没有一个是完美的答案,但是实际上...

“无”肯定不是正确的答案。如果没有显着提高性能,则保存GC毫无意义。如果您追求目标,那么您将无所事事地花费开发时间。您当然可以采用“正常”数量的GC。

没有办法给出一个正常的数字。与花在GC上的时间(这是您必须支付的开销)以及客户等待页面加载所需的G2暂停时间有关。

  

即使请求数量保持不变,GC收集的数量也可能会像这样增加吗?

您的代码中有些可怕的内容,我会说:)也许线程不断旋转以调用GC.Collect()?噩梦成真。 Grep您的代码并报告。我将扩展此答案以帮助您进行调查。

使用PerfView或某些探查器(我使用JetBrains),您应该能够看到代码中GC的触发位置。

答案 1 :(得分:2)

听起来像您已经排除了任何流氓GC.Collect()的通话(除非您的第三方库行为异常)。

鉴于花在垃圾收集上的时间,值得检查一下托管堆上对象的分配率是否有所增加。您应该在Application Insights中添加性能计数器,以监视每秒分配的字节数。但是,如果您不坚持使用它们,则不一定是问题。

正如您所说的那样,流量并不会因此而增加,这很可能是对象幸存于集合中的问题-这会增加集合的持续时间。可能是一些仍然可以到达的临时对象。您将需要使用内存探查器来对此进行更仔细的研究。

还可以与同一时间段内的内存使用情况进行比较,以帮助了解收集的触发条件。

探查器是自然而然的下一步,可以通过性能计数器在确定问题之后进行更清晰的诊断。尤其是遇到这样的重大问题时,相对容易发现问题出在哪里-否则我只是在猜测。还应该能够确认没有来自任何库的强制(GC.Collect())集合。

最简单的起点正在运行Process Explorer-选择进程,右键单击并选择“属性”,然后选择“ .NET Performance”选项卡。任何强制GC都将显示在# Induced GC下,因此您可以检查流氓库。

如果存在,则可以使用WinDbg在GC.Collect()上断开:

!bpmd mscorlib.dll System.GC.Collect

如果被击中,您可以使用以下命令查看它在堆栈跟踪中的位置:

!DumpStack

答案 2 :(得分:0)

让我们先为后代学习一些东西:

第0代。这是最年轻的一代,包含寿命短的物体。短暂对象的一个​​示例是临时变量。垃圾收集在这一代中最常见。

新分配的对象构成新一代的对象,并且是隐式的第0代集合,除非它们是大对象,在这种情况下,它们将进入第2代集合中的大对象堆。

大多数对象在第0代中被回收以进行垃圾回收,并且不能存活到下一代。

第1代。这一代包含短期对象,并充当短期对象和长期对象之间的缓冲区。

第二代。这一代包含长期存在的对象。一个寿命很长的对象的例子是服务器应用程序中的一个对象,其中包含在整个过程中都处于活动状态的静态数据。

在垃圾回收中未被回收的对象称为幸存者,并被提升到下一代。幸存于第0代垃圾回收的对象将提升为第1代;在第1代垃圾回收中幸存的对象将提升为第2代;并且在第2代垃圾回收中幸存的对象仍保留在第2代中。

当垃圾收集器检测到某个世代的生存率很高时,它会增加该世代的分配阈值,因此下一个垃圾回收将获得相当大的回收内存大小。 CLR持续权衡两个优先事项:不要让应用程序的工作集变得太大,不要让垃圾回收花费太多时间。

临时段的大小取决于系统是32位还是64位以及运行的垃圾收集器的类型而变化。下表显示了默认值。

                                   32-bit                      64-bit

工作站GC 16 MB 256 MB

服务器GC 64 MB 4 GB

具有4个逻辑CPU的服务器GC 32 MB 2 GB

具有> 8个逻辑CPU的服务器GC 16 MB 1 GB

临时段可以包含第2代对象。第2代对象可以使用多个段(在您的过程需要和内存允许的范围内)。

从临时垃圾收集中释放的内存量限于临时段的大小。释放的内存量与死对象所占用的空间成比例。

参考:https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals