我有一个处理服务器数据的网络客户端。
数据作为一系列消息发送,这些消息本身就是键/值集合,在概念上类似于HTTP头(除了没有“消息体”),这是一个典型的单向消息(由{{分隔的行) 1}}):
\r\n
我的协议客户端通过使用Response: OK
Channel: 123
Status: OK
Message: Spectrum is green
Author: Gerry Anderson
Foo123: Blargh
和NetworkStream
逐个字符地读取StreamReader
来工作,并使用状态机解析器和while( (nc = rdr.Read()) != -1 )
实例填充StringBuilder
个实例。然后将这些Dictionary实例保存到内存结构中以便进一步处理,它们通常具有大约10分钟的有用寿命。
我的客户端每小时收到数千条这样的消息,而且客户端进程持久 - 这是一个问题,因为我的客户端进程经常增长,从这些Dictionary<String,String>
实例中消耗超过2GB的内存 - 我使用windbg看看所有记忆的去向。这是一个问题,因为代码在只有3.5GB内存的Azure VM上运行。我认为我的程序最多只能消耗超过几百MB的RAM。通常我会坐在虚拟机上观察我的进程随着时间的推移消耗的内存消耗,它会稳定地增长到大约2GB,然后随着GC的收集运行突然下降到大约100MB,然后它会再次增长。 GC运行之间的时间可能不同,根本没有可预测性。
因为这些字符串中有很多是相同的(例如键String
,Response
等)以及Status
和OK
之类的已知值,我可以使用字符串实习来减少使用量,如下所示:
Fail
问题是我看到了额外优化的空间:// In the state-machine parser after having read a Key name:
String key = stringBuilder.ToString();
key = String.Intern( key );
// etc... after reading value
messageDictionary.Add( key, value );
将分配一个新的字符串实例,该实例将用于实习,其次:实习字符串在appdomain的生命周期中持续存在,不幸的是一些密钥不会重复使用,实际上会浪费内存,例如我的协议示例中的sb.ToString()
。
我认为一个解决方案是不使用字符串实习,而是使用包含Foo123
字符串字段的类,这些字符串是已知密钥,然后使用普通的非内部字符串 - 最终将是GC'd不要冒险用一次性字符串填充字符串实习池。然后我会将static readonly
实例与这些已知字符串进行比较,如果是,请使用它们而不是调用StringBuilder
,从而跳过另一个字符串分配。
但是,如果我确实选择实习每个字符串,实习池将继续增长,不幸的是.NET似乎没有字符串池的sb.ToString()
方法,有没有办法删除如果我继续使用.Chlorinate()
方法,那么来自实习池的一次性字符串,还是使用我自己的静态只读字符串实例更好?
答案 0 :(得分:2)
由于你引用的原因,实习在这里没有用。它实际上会使事情变得更糟,因为实习的字符串不再受垃圾收集的影响。不,没有方法可以从池中删除被拦截的字符串。
您已经描述了GC正在完成GC的设计目的,所以我真的不清楚您确实遇到了问题。采用实习意味着交易垃圾收集(这不是一个问题),以满足不断增长的内存需求(这是一个问题)。
如果您担心GC的运行频率不足以使您的内存消耗低于某个阈值,您可能会考虑监控内存使用情况并在达到该阈值时调用GC.Collect()。 / p>
如果GC的行为模式实际上导致了问题(除了看起来很奇怪),那么您可能想尝试从默认的“工作站”GC模式切换到“服务器”GC模式,因为它们的调整方式不同。 (但是,我再也不相信你确实遇到了问题。)
这两个页面涵盖了一些差异:
但请注意,实际差异随着框架的每个版本而变化,因为负责这些内容的人员不断学习和改进。
GC模式由app config控制:
http://msdn.microsoft.com/en-us/library/cc165011(v=office.11).aspx
<configuration
<runtime>
<gcServer enabled="true"/>
</runtime>
</configuration>
您可能还会发现此故障排除指南很有用,或者至少很有趣:
http://msdn.microsoft.com/en-us/library/ee851764(v=vs.110).aspx#Issue_TooMuchMemory