.NET 垃圾回收问题。程序阻塞 15-40 分钟。

8
一些事实: 我们开发了一个WCF服务,它充当客户端和数据库之间的层。它是自托管的,并作为Windows服务运行。
该服务保持了几个缓存,其中最大的缓存在1-2GB内存范围内。总内存使用量通常约为5-8GB。连接是双工的,使用TCP协议,并且序列化是用protobuf-net完成的。我们连接的客户端数通常在1000-1500之间。
服务器是一台新型的8核Xeon,具有64GB内存,并且只运行服务。
问题:在一定时间后,从一天到一周不等,服务变得极其缓慢。需要0.5秒的请求可能需要超过一分钟。这种行为会持续15-40分钟,或直到重新启动服务。
我们所做的: 我们已经检查了网络和与服务器的网络连接,没有问题。此时CPU利用率稍微上升,例如平均值从30%增加到40-50%的平均值。 我们已经获取了内存转储,代码中没有逻辑锁定阻止用户,并且基本上没有任何活动。 我们最近的线索是垃圾回收器。在perfmon中,我们可以看到“%time in gc”不断超过90%(90-97%),并且收集计数上升。两者都是GC0和GC1。我们怀疑还有一个阻塞的GC2正在运行,但由于这是在生产中,所以我们不得不重新启动服务,因此它没有在我们运行perfmon的5分钟窗口期间计算。内存使用量为7.6 GB。 注意:未处理调用会增加,因此调用会到达,但服务无法处理它们。
我的问题是,垃圾回收器是否可能处于运行和阻止状态超过15分钟?或者问题可能与其他问题有关?
我们的服务在工作站模式下运行GC,并且latencymode:Interactive 现在我们已将其更改为Server和SustainedLowLatency,并希望这会有所帮助。如果是垃圾回收器,我们还能做些什么吗?
编辑:大内存使用是设计要求,缓存中的数据很大,并且还有很多可用内存。

建议找出内存使用率高的根本原因...例如,尝试添加“using”块,在使用完该对象后释放内存。 - User2012384
只是出于好奇,你有多少个线程?在任务管理器中检查。至少几年前存在一个问题,即线程越多(即使是空闲的),GC就会变得越慢。 - xanatos
2
垃圾回收器是否可能处于运行并持续阻塞超过15分钟的状态?完全有可能,如果它不断需要释放内存但由于您一直在占用而无法释放。正如雷蒙德·陈所说,“具有糟糕策略的缓存是内存泄漏的另一个名称”。 - Jeroen Mostert
2
高内存使用是有意的,我们缓存的数据很大,我们希望它被缓存。我认为我们不能让1500个客户端生成SQL查询,因为那会影响整体性能。 内存使用量不会随时间增长,而是每天基本保持不变。一天5GB,另一天7GB,取决于每天的使用情况。缓存中的数据在4小时内无活动时(按项级别)被释放,因此它在白天积累,在晚上释放。 机器上至少有50GB的可用内存。 - Johan Lindberg
1
一个大的缓存会导致大对象堆碎片化,从而阻止垃圾回收器有效工作。如果您使用的是.NET 4.5.1或更高版本,则可以尝试使用LargeObjectHeapCompactionMode来解决问题。如果您仍在使用.NET 4.0,则一定要升级至至少4.5,因为它改进了LOH分配。所有这些都假设LOH确实是您的问题;诊断内存问题总体而言是一个广泛的话题,不适合简短回答。 - Jeroen Mostert
显示剩余2条评论
2个回答

4

过度的垃圾回收通常是由代码问题引起的。您可能在短时间内创建了太多对象,或者您保持分配内存而不释放它。

实际上,在 MSDN 上有一个广泛的清单可用,应该可以帮助您诊断问题。

非常大的 GC2 表示其中的对象经历了多次垃圾回收,这意味着它们在内存中保存的时间更长。这可能是您的问题的根本原因。也许有一个缓存机制需要进行一些调整/保留策略(删除长时间未使用的数据)。


我们最大的缓存是一个不可变集合,当需要添加数据时会添加,如果数据不存在则添加。如果该项在4小时内没有再次访问,则计数器被重置。因此它非常简单。 一种常见的情况是它在一天早些时候增长到1-1.5GB,然后添加0.5GB新数据并删除0.5GB,晚上完全清除。 如果是缓存引起了问题,我想在白天能够告诉垃圾回收程序根本不要触碰它,并在晚上扫描它。 如果我设置了Sustainedlowlatency并在晚上强制进行gc.collect,我能得到这种行为吗? - Johan Lindberg
Johan:如果你设置了它,垃圾回收仍然会比一天更频繁地进行。GC是一个持续的过程。请注意GCLatencyMode中的备注部分:“如果系统承受内存压力,则仍可能发生完全阻塞的集合。” - Patrick Hofman
如果我想在.NET中拥有一个大的持久缓存,那么我该如何实现它,以使GC不会破坏我的性能?我想要告诉它何时进行检查?使用保留的CLR托管或其他什么方法? - Johan Lindberg
@JohanLindberg:抱歉,这对我来说太高级了。如果您就此提出新问题,我会投票支持您,并甚至设置悬赏以吸引更好的答案。这是我也想要在我的一个项目中实现的功能。请参考这个问题并展示一般思路,就像您现在所做的一样。 - Patrick Hofman
如果有人感兴趣,coderguru Marc Gravell的这篇博客似乎解决了同样的问题。http://blog.marcgravell.com/2011/10/assault-by-gc.html - Johan Lindberg
@JohanLindberg:感谢您的回复。 - Patrick Hofman

1
我有类似的情况。在使用protobuf和WCF进行客户端通信的服务中,大型数据库数据缓存。缓存不仅仅是为了客户端,业务层也使用缓存来执行操作。服务的内存占用量可以在2到10 GB之间。我在8小时不活动后释放缓存的一部分。机器有8个虚拟核心和32 GB的内存。我正在使用.Net 4.5.1。
当我从数据库加载缓存时,GC会立即消耗98%的CPU一个小时。有趣的是,在我们两种情况下都没有任何内存压力。
我认为GC是无论如何都要执行的,因为某些事情发生了,GC尝试为所有线程保留可用内存。由于一个线程在加载缓存时分配了大量内存,所以GC被触发了。我必须做几件事才能解决它。
1)从缓存中删除元组。我将它们用作字典键,它们的StructuralEquality实现很糟糕。它将所有属性作为对象进行比较,因此对于值属性,会发生大量装箱操作,这些操作最终需要进行垃圾回收。

2) 当替换用作键的元组时,我无法简单地将它们替换为结构体,因为值比较使用反射,这太耗费资源了,所以最终我创建了一个通用的Pair结构体。当它们在数组中时,我决定使用结构体来减少对象的数量。

3) 为了删除元组,我不得不创建自己的Pair结构体,使用属性类型的默认相等性进行属性比较。本质上与PowerCollections创建的相同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接