CLR #Bytes in all Heaps
性能计数器,在过去的几天里,我注意到了一种模式,这让我觉得Gen2收集器并不总是收集死对象,但我很难理解到底发生了什么。
该服务器应用程序在.NET Framework 4.5.1上运行,使用Server GC / Background。
这是一个控制台应用程序,通过Topshelf框架作为Windows服务托管。
该服务器应用程序正在处理消息,吞吐量目前保持相当稳定。
从CLR #Bytes in all Heaps
图表中可以看到,内存从18MB开始增长到大约在20-24小时内达到35MB(在此时间范围内进行了20-30次Gen2收集),然后突然降回到18MB的名义值,然后再次增长到大约35MB,并且如此循环(我可以看到该模式在过去6天中一直在重复...)。内存增长不是线性的,需要大约5个小时才能增加10MB,然后需要15-17个小时才能增加剩余的10MB左右。
问题在于,通过查看#Gen0/#Gen1/#Gen2 collections
的perfmon计数器,可以看到在20-24小时的时间段内进行了大量的Gen2收集(可能有30个),但没有一个使内存降回到名义的18MB。然而,奇怪的是,通过使用外部工具来强制进行GC(在我的情况下为Perfview),然后我可以看到#Induced GC
上升了1个(调用了GC.Collect,因此这是正常的),并且立即将内存恢复到名义的18MB。这让我想到,要么#Gen2收集器的计数器不正确,在20-22小时左右只会发生一次单独的Gen2收集(我真的不这么认为),要么Gen2收集器并不总是收集死对象(看起来更有可能)……但是如果是这种情况,为什么通过GC.Collect强制进行垃圾回收就可以解决问题呢?显式调用GC.Collect和应用程序生命周期中自动触发的垃圾回收之间有什么区别。我相信有一个很好的解释,但从我找到的关于GC的不同文档来源-太少了 :( - 可以得出:无论如何,Gen2收集器都会收集死对象。所以也许文档不是最新的,或者我误读了......欢迎任何解释。谢谢!
编辑: 请查看4天内
#Bytes in all heaps
图表的截图(点击查看更大的视图)
https://istack.dev59.com/NyCsn.webp 这比试图在脑海中绘制图形容易得多。您可以在图表上看到我上面说的东西...内存在20-24小时内增加(在此期间进行了20-30次Gen2收集),直到达到约35MB,然后突然下降。您将注意到在图表末尾,我通过外部工具触发的诱导GC立即将内存降至名义上的值。
编辑#2: 我对代码进行了很多清理,主要涉及终结器。我有很多类引用了可丢弃类型,因此必须在这些类型上实现IDisposable。然而,我被一些文章误导成无论如何都要在Disposable模式中实现终止器。阅读了一些MSDN文档后,我明白了只有当类型本身持有本机资源时才需要终止器(即使在这种情况下也可以使用SafeHandle避免)。所以我从所有这些类型中删除了终结器。代码还有一些其他修改,但主要是业务逻辑,与.NET框架无关。现在,这张图表看起来非常不同,它是一条大约20MB的平直线,已经持续了好几天......正是我所期望看到的!现在问题已经解决,但是我仍然不知道问题的原因......似乎可能与finalizers有关,但仍然无法解释我所注意到的情况,即使我们没有调用Dispose(true)-抑制finalizer-,finalizer线程也应该在垃圾回收之间启动,而不是每20-24小时启动?考虑到我们现在已经远离了这个问题,需要时间回到“有缺陷”的版本并再次重现它。尽管如此,我可能会尝试一下并找到问题的根源。
编辑:添加了Gen2收集图表(点击查看更大的视图)