我正在使用.Net 4中的新MemoryCache,最大缓存大小限制为MB(我测试它设置在10到200MB之间,系统内存介于1.75和8GB之间)。我没有在对象上设置任何基于时间的过期,因为我将缓存简单地用作高性能驱动器,并且只要有空间,我希望它被使用。 令我惊讶的是,缓存拒绝驱逐任何对象,以至于我会得到SystemOutOfMemory
例外。
我启动 perfmon ,将我的应用程序连接到.Net CLR Memory\#Bytes In All Heaps
,.Net Memory Cache 4.0
和Process\Private Bytes
- 事实上,内存消耗已经失控,并且没有注册缓存修剪。
一些谷歌搜索和堆栈溢出,下载并附加CLRProfiler和 wham :驱逐无处不在!根据我设置的内存大小限制,内存保持在合理的范围内。再次调试模式,没有驱逐。 CLRProfiler再次驱逐。
我终于注意到,探查器强制应用程序在没有concurrent garbage collection的情况下运行(另见有用的SO Concurrent Garbage Collection Question)。我在我的app.config中将其关闭了,果然,驱逐!
这似乎充其量只是缺乏文档而没有说:这只适用于非并发垃圾收集 - 虽然我映像自从它移植到ASP.NET后,它们可能不必担心并发垃圾收集。
有没有人见过这个?我希望得到一些其他经验,也许还有一些更有教育意义的见解。
更新1
我在一个方法中重现了这个问题:似乎必须并行写入缓存,以便缓存驱逐不被触发(在并发垃圾收集模式下)。如果有兴趣,我会将测试代码上传到公共回购。我肯定会走向CLR / GC / MemoryCache池的深层,我想我忘了我的漂浮物......
更新2
我发布了test code on CodePlex来重现这个问题。此外,可能感兴趣的是,原始生产代码在Azure中作为工作者角色运行。有趣的是,在角色的app.config中更改GC并发设置无效。 Azure可能会像ASP.NET一样覆盖GC设置吗?此外,在WPF和控制台应用程序下运行测试代码将产生略微不同的驱逐结果。
答案 0 :(得分:8)
您可以在有问题的方法之后立即“强制”垃圾收集,并查看问题是否重现执行:
System.Threading.Thread.Sleep(200);
GC.Collect();
GC.WaitForPendingFinalizers();
在方法的最后(确保释放任何句柄以引用对象并将其清空)。如果这可以防止内存泄漏,然后是,则可能存在运行时错误。
答案 1 :(得分:4)
Stop-the-world垃圾收集基于确定在世界停止时是否存在对对象的强实时引用。并发垃圾收集通常确定自上一个特定时间以来是否存在对对象的强实时引用。我的猜想是,WeakReferences中保存的对象的许多强引用都是单独创建和丢弃的。如果一个stop-the-world垃圾收集器在创建一个特定对象和它被丢弃的时间之间触发,那么该特定对象将保持活动状态,但之前丢弃的对象不会。相比之下,并发垃圾收集器可能无法检测到对象的所有强引用都被丢弃,直到一定时间过去而没有对正在创建的对象的任何强引用。
我有时希望.net会在强引用和弱引用之间提供某种东西,这会阻止对象从内存中擦除,但不会保护它不被最终确定或弱弱WeakReferences无效。这样的引用会使GC过程稍微复杂化,要求每个对象都有单独的标志,指示是否存在强弱和准弱引用,以及是否已经扫描了强引用和准弱引用,但这样的特性可能会有所帮助在许多“弱势事件”情景中。
答案 2 :(得分:2)
我在搜索类似的主题时发现了这个条目,而且我专注于你的内存异常。
如果你将一个对象放在缓存中,那么它仍然可能引用其他对象,因此这些对象不会被垃圾收集 - 因此内存不足异常,并且可能由于Gen 2垃圾收集而导致CPU被挂起。
您是将“已使用”对象放在缓存上还是缓存中“已使用”对象的克隆上?如果将克隆放在缓存上,那么可能引用其他对象的“used”对象可能会被垃圾收集。
如果关闭缓存机制,程序是否仍会耗尽内存?如果它没有内存耗尽那么这将证明你原本放在缓存上的对象仍然保留对阻碍垃圾收集的其他对象的引用。
强制垃圾收集不是最佳做法,不应该这样做。在这种情况下,强制垃圾收集无论如何都不会处理引用的对象。
答案 3 :(得分:0)
MemoryCache肯定有一些问题。它在我的asp.net服务器上吃了160Mb的内存,只是更改为简单列表并添加了一些额外的逻辑来获得相同的功能。