【发布时间】:2017-02-28 22:01:35
【问题描述】:
我有一个继承的 .NET 4.0 应用程序,它作为 Windows 服务运行。无论如何,我都不是 .NET 专家,但在编写了 30 多年的代码之后,我知道如何找到自己的出路。
当服务首次启动时,它的私有工作集大小约为 70MB。服务运行的时间越长,占用的内存就越多。增长并没有那么显着,以至于您只是坐着看的时候就注意到了,但是我们已经看到了在应用程序运行很长时间(100 多天)后,它达到了数 GB(5GB 是当前记录)的实例。我将 ANTS Memory Profiler 附加到一个正在运行的实例上,发现 ExpandoObject 的使用似乎导致了几兆字节的字符串没有被 GC 清理。可能还有其他泄漏,但这是最引人注目的,因此它首先受到了攻击。
我从其他 SO 帖子中了解到,在读取(但不写入)动态分配的属性时,ExpandoObject 的“正常”使用会生成内部 RuntimeBinderException。
dynamic foo = new ExpandoObject();
var s;
foo.NewProp = "bar"; // no exception
s = foo.NewProp; // RuntimeBinderException, but handled by .NET, s now == "bar"
您可以在 VisualStudio 中看到异常发生,但最终它在 .NET 内部进行处理,您得到的只是您想要的值。
除了... 异常的 Message 属性中的字符串似乎停留在堆上并且永远不会被垃圾收集,即使在生成它的 ExpandoObject 超出范围很久之后也是如此。
简单示例:
using System;
using System.Dynamic;
namespace ConsoleApplication2
{
class Program
{
public static string foocall()
{
string str = "", str2 = "", str3 = "";
object bar = new ExpandoObject();
dynamic foo = bar;
foo.SomePropName = "a test value";
// each of the following references to SomePropName causes a RuntimeBinderException - caught and handled by .NET
// Attach an ANTS Memory profiler here and look at string instances
Console.Write("step 1?");
var s2 = Console.ReadLine();
str = foo.SomePropName;
// Take another snapshot here and you'll see an instance of the string:
// 'System.Dynamic.ExpandoObject' does not contain a definition for 'SomePropName'
Console.Write("step 2?");
s2 = Console.ReadLine();
str2 = foo.SomePropName;
// Take another snapshot here and you'll see 2nd instance of the identical string
Console.Write("step 3?");
s2 = Console.ReadLine();
str3 = foo.SomePropName;
return str;
}
static void Main(string[] args)
{
var s = foocall();
Console.Write("Post call, pre-GC prompt?");
var s2 = Console.ReadLine();
// At this point, ANTS Memory Profiler shows 3 identical strings in memory
// generated by the RuntimeBinderExceptions in foocall. Even though the variable
// that caused them is no longer in scope the strings are still present.
// Force a GC, just for S&G
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.Write("Post GC prompt?");
s2 = Console.ReadLine();
// Look again in ANTS. Strings still there.
Console.WriteLine("foocall=" + s);
}
}
}
“bug”在旁观者的眼中,我想(我的眼睛说 bug)。我错过了什么吗?这是正常的并且是组中的 .NET 大师所期望的吗?有什么办法可以告诉它清除这些东西吗?首先不使用动态/ExpandoObject 的最佳方法是什么?
【问题讨论】:
-
如果您使用 Task.Factory.StartNew 将 foocall 放在它自己的线程上,泄漏是否仍然存在?
-
是的,但它只显示异常字符串的 3 个实例,无论我触发了多少线程。
-
已确认,此内存泄漏问题发生在 Winform 项目中,同时使用 .NET framework 4.7.2
标签: .net memory-leaks expandoobject