【发布时间】:2013-12-27 02:30:18
【问题描述】:
通过在过去几天监视一个全新的 .NET 4.5 服务器应用程序的 CLR #Bytes in all Heaps 性能计数器,我注意到一种模式,让我认为 Gen2 收集并不总是收集死对象,但我遇到了麻烦了解到底发生了什么。
服务器应用程序正在 .NET Framework 4.5.1 中使用服务器 GC/后台运行。
这是作为 Windows 服务托管的控制台应用程序(借助 Topshelf 框架)
服务器应用程序正在处理消息,并且吞吐量目前相当稳定。
查看CLR #Bytes in all Heaps 的图表我可以看到,内存开始时约为 18MB,然后在大约 20-24 小时内增长到 35MB(在此时间范围内有 20-30 次 Gen2 集合),然后全部突然回落到 18MB 的标称值,然后在 20-24 小时内再次增长到 ~35MB,然后回落到 18MB,依此类推(我可以看到在过去 6 天应用程序正在运行的模式重复出现) ...内存的增长不是线性的,增长 10MB 大约需要 5 个小时,然后剩下的 10MB 大约需要 15-17 个小时。
问题是我可以通过查看#Gen0/#Gen1/#Gen2 collections 的性能计数器看到,在 20 到 24 小时期间(可能大约 30 小时)发生了一堆 Gen2 收集,并且它们都没有使内存回落到正常值18MB。
然而,奇怪的是通过使用外部工具强制 GC(在我的情况下为 Perfview),然后我可以看到 #Induced GC 上升 1(GC.Collect 被调用,所以这是正常的)并且内存立即消失回到标称的 18MB。
这让我想到 #Gen2 集合的性能计数器不正确,并且在 20-22 小时左右后仅发生一个 Gen2 集合(我真的不这么认为)或者 Gen2 集合不总是收集死对象(似乎更合理)......但在那种情况下,为什么要通过 GC.Collect 强制 GC 来解决问题,显式调用 GC.Collect 与在生命周期内自动触发的收集有什么区别应用程序。
我确信有一个很好的解释,但是从我发现的关于 GC 的不同文档来源 - 太少了 :( - Gen2 集合在任何情况下都会收集死对象。所以也许文档不是最新的或我看错了...欢迎任何解释。谢谢!
编辑:请查看#Bytes in all heaps 4 天内的图表截图
(点击查看大图)
这比试图在脑海中绘制图表更容易。您可以在图表上看到我在上面所说的内容...内存在 20-24 小时内增加(以及在该时间范围内 20-30 次 Gen2 集合),直到达到约 35MB,然后突然下降。您会注意到,在图表的末尾,我通过外部工具触发了诱导 GC,立即将内存回退到标称值。
编辑#2:我在代码中做了很多清理工作,主要是关于终结器。我有很多类都引用了一次性类型,所以我必须在这些类型上实现IDisposable。但是,无论如何我都被一些文章误导了使用终结器来实现可丢弃模式。在阅读了一些 MSDN 文档后,我了解到只有当类型本身拥有本机资源时才需要终结器(在这种情况下,使用 SafeHandle 可以避免这种情况)。所以我从所有这些类型中删除了所有终结器。代码中还有其他一些修改,但主要是业务逻辑,与“.NET 框架”无关。
现在的图表非常不同,这是一条大约 20MB 的扁平线,已经好几天了……这正是我所期待的!
所以问题现在已经解决了,但是我仍然不知道问题是什么引起的......它似乎可能与终结器有关,但仍然没有解释我注意到的内容,即使我们没有调用 Dispose (真)-抑制终结器-,终结器线程应该在收集之间而不是每 20-24 小时启动一次?!
考虑到我们现在已经摆脱了这个问题,需要时间才能回到“错误”版本并再次重现它。不过,我可能会尝试做一些时间并深入了解它。
编辑:添加 Gen2 集合图 (点击查看大图)
【问题讨论】:
-
您确定您的应用不会泄漏内存吗?
-
存在第三种可能性,即第二代集合中的对象在您认为它们已死时并未死。我会使用分析工具来跟踪对象的生命周期,看看你是否有一些长期存在的对象(可以存活 20-22 小时)在不应该存在的时候保留引用。此外,您从未说过大型收集之前和之后的对象计数,您是否有一个需要 20 小时才能释放的 17MB 对象,或者您是否有数百个小对象由于一个长期存在的对象而保持活动状态对所有这些的引用。
-
@Scott :是的,但在这种情况下,为什么诱导 GC 会收集这些对象并返回到标称内存,而在应用程序的生命周期内正常的 Gen2 GC 不会这样做。请参阅附图以获得更好的理解。
-
@usr :似乎我有大约 1MB / 24 小时的内存泄漏,我正在对此进行调查,但似乎与我在图表上看到的内容无关。
-
您的应用程序池的回收频率是多少?
标签: c# .net garbage-collection .net-4.5