为什么在调用ServiceHost.Open之前实例化XmlSerializer会产生一个内存和放大器。处理泄漏

时间:2012-10-26 11:23:09

标签: c# .net wcf windows-services xml-serialization

在.NET / WCF / Windows服务中查找内存和句柄漏洞时,我发现了一些我无法解释的奇怪行为。 这里的设置和分辨率。我正在寻找的是对观察到的行为的解释。

我安装了Windows服务。
我开始服务了。 我用事务性WCF调用调用了一个简单的方法(每个调用的新通道 - 没有缓存)。
对于每次调用,大约有2个句柄留在内存中。

如果以下项目适用,则可以观察到:

  1. 这是一项Windows服务;不要将其作为控制台应用程序运行。
  2. 使用事务(仅测试单独的进程或计算机)来调用WCF方法。
  3. 在调用ServiceBase.Run(servicesToRun);实例化某种类型的XmlSerializer之前。
  4. 该类型是自定义类型。 new XmlSerializer(typeof(string))或新XmlSerializer(typeof(XmlDocument))不会发生这种情况。不需要调用序列化。如果自定义类型只有一个字符串作为属性(没有任何句柄!)
  5. 就足够了
  6. 使用SGen.exe创建静态XmlSerialization.dll不会产生此问题。
  7. 我的代码已包含修复:
    在OnStart()中使用XmlSerializer 最早

    Program.cs的

    WindowsService winSvc = new WindowsService();
    ServiceBase[] servicesToRun = new ServiceBase[]{winSvc};                    
    ServiceBase.Run(servicesToRun);
    

    WindowsService.cs

    internal sealed class WindowsService : ServiceBase
    {
        private ServiceHost wcfServiceHost = null;
    
        internal WindowsService()
        {
            AutoLog = true;
            CanStop = true;
            CanShutdown = true;
            CanPauseAndContinue = false;
        }
    
        internal void StartWcfService()
        {
            wcfServiceHost = new ServiceHost(typeof(DemoService));
            wcfServiceHost.Open();
        }
    
        protected override void Dispose(bool disposing)
        {
            if (wcfServiceHost != null)
            {
                wcfServiceHost.Close();
            }
    
            base.Dispose(disposing);
        }
    
        protected override void OnStart(string[] args)
        {
            new XmlSerializer(typeof(MyType));
    
            StartWcfService();
        }
    }
    

    DemoService.cs

    [ServiceBehavior
        (
            InstanceContextMode = InstanceContextMode.PerSession,
            TransactionAutoCompleteOnSessionClose = false,
            IncludeExceptionDetailInFaults = true
        )
    ]
    public sealed class DemoService : IDemoService
    {           
        [TransactionFlow(TransactionFlowOption.Allowed)]
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public int Add(int a, int b)
        {
            return a + b;
        }
    }
    

    Client.cs

    IChannelFactory<IDemoService> channelFactory = new ChannelFactory<IDemoService>("defaultClientConfiguration");
    IDisposable channel = null;
    for (int index = 0; index < 5000; index++)
    {
        using
        (
            channel = (IDisposable)channelFactory.CreateChannel(new EndpointAddress("net.tcp://localhost:23456/DemoService")))
            {                       
            IDemoService demoService = (IDemoService)channel;
            using (TransactionScope tx = new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                demoService.Add(3, 9);
                tx.Complete();  
            }
        )
    }
    

    有人可以解释这种行为吗?

    请注意,我不想找到避免泄漏的方法(我已经知道如何做到这一点)但是在解释中(即为什么会发生这种情况)。

2 个答案:

答案 0 :(得分:7)

我认为一些内部运作这个问题是正义的。我从头脑中做到这一点,因为我前段时间遇到了这个问题,我花了一天的时间来追踪,包括大量使用Reflector和ANTS Memory Profiler(在我以前的公司)......这里云:

XML Serializer在内部做的是使用System.Reflection.Emit创建一个类(让我们称之为'A'),它接受传递给它的类型。构建这样的类相对来说花费了大量时间,并且可以重复使用,因为类型不会改变。因此,构造的类型存储在字典中,例如,它以一些字典结束。

对于已知(基本)类型,序列化器代码是固定的,例如无论重启应用程序多少次,字符串的序列化都不会改变。请注意与“A”的区别,其中序列化工厂未知的任何类型,直到它首次传递给XMLSerializer。

XMLSerializer第一次使用该类型时,会对您传递的类型及其所需的所有类型(例如,需要序列化的所有字段和属性)执行此过程。

关于泄漏......当你调用ChannelFactory时,如果它还不存在,它会构造序列化器。为此,它检查字典中是否已存在序列化程序,如果不存在,则通过创建ISomeSerializerType实例来创建一个。

出于某些愚蠢的原因,工厂中存在构造新序列化程序而不将其存储在字典中的错误。构建完成后,最终会出现一个新类型 - 显示为泄漏(请记住:类型永远不会被卸载) - 即使对象被正确处理。首先使用XMLSerializer或创建静态类时,它会正确使用Dictionary缓存,这意味着它不会泄漏。所以,你有它,这是一个错误。我曾经访问过ANTS Memory Profiler,它很好地展示了这一点。

希望这能解释。

答案 1 :(得分:6)

XmlSerializer文档说明了这一点:

  

为了提高性能,XML序列化基础结构动态生成程序集以序列化和反序列化指定的类型。基础结构查找并重用这些程序集。仅当使用以下构造函数时才会出现此问题:

     

XmlSerializer.XmlSerializer(类型):

     

XmlSerializer.XmlSerializer(Type,String)

     

如果使用任何其他构造函数,则会生成同一程序集的多个版本,并且永远不会卸载,这会导致内存泄漏和性能下降。最简单的解决方案是使用前面提到的两个构造函数之一。否则,必须将程序集缓存在Hashtable中,如以下示例所示。

http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx