我有一个继承的.NET 4.0应用程序,作为Windows服务运行。虽然我不是.NET专家,但在编写代码30年后,我知道如何找到自己的方式。
当服务首次启动时,它的私有工作集约为70MB。服务运行时间越长,占用内存越多。增加并不那么明显,只是在观察时不会注意到。但我们曾经看到过这样的情况,应用程序运行了很长时间(100多天),内存占用量达到了数GB(5GB是当前记录)。我连接了ANTS Memory Profiler到正在运行的实例,并发现ExpandoObject的使用似乎占用了多个兆字节的字符串,这些字符串没有被GC清理。可能还有其他泄漏,但这是最明显的问题,因此首先对其进行了攻击。
从其他SO帖子中我了解到,“正常”的ExpandoObject使用会在读取(但不是写入)动态分配属性时生成内部RuntimeBinderException。
您可以在VisualStudio中看到异常发生,但最终它是在.NET内部处理的,并且您获得的只是想要的值。
除了... 异常的Message属性中的字符串似乎仍然停留在堆上,即使生成它的ExpandoObject已经超出作用域,它也永远不会被垃圾回收。
简单的例子:
“Bug”可能是在观察者眼中的,我想(我的眼睛也这么认为)。我是否遗漏了什么?.NET大师们是否认为这是正常和预期的?有没有一种方法可以让它清除掉东西?最好的方法是根本不要使用dynamic / ExpandoObject吗?
当服务首次启动时,它的私有工作集约为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”可能是在观察者眼中的,我想(我的眼睛也这么认为)。我是否遗漏了什么?.NET大师们是否认为这是正常和预期的?有没有一种方法可以让它清除掉东西?最好的方法是根本不要使用dynamic / ExpandoObject吗?