【问题标题】:Memory Leak using StreamReader and XmlSerializer使用 StreamReader 和 XmlSerializer 的内存泄漏
【发布时间】:2014-05-27 19:09:42
【问题描述】:

过去几个小时我一直在谷歌上搜索并尝试不同的东西,但似乎无法深入了解......

当我运行这段代码时,内存使用量不断增长。

while (true)
{
    try
    {
        foreach (string sym in stringlist)
        {
            StreamReader r = new StreamReader(@"C:\Program Files\" + sym + ".xml");
            XmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));
            XMLObj obj = (XMLObj)xml.Deserialize(r);                       
            obj.Dispose();
            r.Dispose();
            r.Close();
        }
    }    
    catch(Exception ex) 
    {
        Console.WriteLine(ex.ToString()); 
    }
    Thread.Sleep(1000);
    Console.Clear();
}

XMLObj 是一个自定义对象

[Serializable()]
public class XMLObj: IDisposable
{
    [XmlElement("block")]
    public List<XMLnode> nodes{ get; set; }

    public XMLObj() { }

    public void Dispose()
    {
        nodes.ForEach(n => n.Dispose());
        nodes= null;

        GC.SuppressFinalize(this);
    }
}

我尝试添加 GC.Collect();但这似乎没有任何作用。

【问题讨论】:

  • 如果您认为GC.Collect 解决了“内存泄漏”问题,那么您找错地方了:blogs.msdn.com/b/ricom/archive/2003/12/02/40780.aspx。仅仅因为内存使用量上升,并不意味着您有内存泄漏。我还建议研究垃圾收集是一般性的:msdn.microsoft.com/en-us/library/0xy59wtx(v=vs.110).aspx
  • XmlNode 没有实现 IDisposable。如果你的类没有终结器,你就不需要调用 GC.SuppressFinalize。使用 PerfView (microsoft.com/en-us/download/details.aspx?id=28567) 查看谁拥有你的记忆。我认为您发布的代码根本没有泄漏。
  • 这不是问题的答案,但请考虑将 XmlSerializer 实例的构造移到循环之外。这可能会提高性能并减少内存开销。
  • 我在这里看到了这篇出色的博客文章,它解释了为什么 XmlSerializer 会导致内存泄漏:techknackblogs.com/2012/10/xmlserializer-may-cause-memory-leak 此外,您应该使用 using () { } 语句来处理您的问题即使在异常情况下资源。在您的代码中,如果抛出异常,则不会释放资源。
  • @DanBryant 您的建议,虽然从预编译的角度来看是准确的,但您会惊讶于现在的高级编译器(我做了一些测试,发现对于某些 foreach,在内部定义变量或外部在 IL 中没有区别)。

标签: c# xml streamreader xmlserializer


【解决方案1】:

泄漏就在这里:

new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))

XmlSerializer 使用程序集生成,无法收集程序集。它为最简单构造函数场景(new XmlSerializer(Type)等)做了一些自动缓存/重用,但对于这种场景不是。因此,您应该手动缓存它:

static readonly XmlSerializer mySerializer =
    new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))

并使用缓存的序列化程序实例。

【讨论】:

  • 只有部分构造函数使用缓存,这背后有什么逻辑吗?
  • @LeonidVasilyev 可能很方便;很容易缓存单一场景的东西;做更复杂的场景将涉及构建一个散列算法和强大的相等性检查来比较不同的配置,用于一些同类的散列表
  • 那行毫无防备的代码也导致了我的应用程序中的内存泄漏;我已经确认了。很棒的发现!
  • 如果您在 foreach 循环中,请在循环之前对其进行初始化 (XmlSerializer mySerializer = new ..) 并将对象 (mySerializer ) 传递给您用于反序列化的方法
  • Marc Gravell 一如既往地进行救援 :) 我已经研究了好几个小时的内存转储。
【解决方案2】:

首先,即使抛出异常,您也应该处理掉您的 StreamReader(对于 XMLObj 也是如此)。使用using 语句。目前你不会在抛出异常时进行处理。

您不太可能发生内存泄漏。更有可能的是,运行时根本没有选择收集内存。即使 GC.Collect 也不一定会导致内存被释放。

在处理非常大的 XML 文件(多 GB)时,我也遇到过类似的情况。即使运行时获取了大部分可用内存,它也会在内存压力允许时释放它。

您可以使用 Visual Studio 中的内存分析器来查看分配了哪些内存,以及它驻留在哪一代。

更新

@KaiEichinger 的评论值得研究。它表明 XmlSerializer 可能正在为每个循环迭代创建一个新的缓存对象定义

XMLSerializer 构造函数为要使用反射进行序列化的类型创建临时程序集,并且由于代码生成成本很高,因此程序集基于每种类型缓存在内存中。但是很多时候根名称会被更改并且可以是动态的并且它不会缓存动态程序集。因此,每当调用上述代码行时,它每次都会加载新的程序集,并且会一直保留在内存中,直到 AppDomain 被卸载。

【讨论】:

  • 这成功了!非常感谢。这是 XmlSerializer 的构造函数在循环中的问题。一旦取出,内存就稳定了。
【解决方案3】:

来自 MSDN:enter link description here

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

XmlSerializer.XmlSerializer(类型)

XmlSerializer.XmlSerializer(Type, String)

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

=> 所以要修复它,你必须使用这个构造函数 XmlSerializer xml = new XmlSerializer(typeof(XMLObj)) 而不是 XmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));

并将根 XML 属性添加到 XMLObj 类中。

[Serializable()]
[XmlRoot("root")]
public class XMLObj: IDisposable
{
    [XmlElement("block")]
    public List<XMLnode> nodes{ get; set; }

    public XMLObj() { }

    public void Dispose()
    {
        nodes.ForEach(n => n.Dispose());
        nodes= null;

        GC.SuppressFinalize(this);
    }
}

【讨论】:

    【解决方案4】:

    我正在使用“缓存”类来避免每次需要序列化某些东西时实例化 xmlserializer(还添加了一个 XmlCommentAttribute 用于将 cmets 添加到 xml 输出中的序列化属性),对我来说它就像 sharm 一样工作,希望有所帮助有这个的人:

     public static class XmlSerializerCache
    {
        private static object Locker = new object();
        private static Dictionary<string, XmlSerializer> SerializerCacheForUtils = new Dictionary<string, XmlSerializer>();
    
        public static XmlSerializer GetSerializer<T>()
        {
            return GetSerializer<T>(null);
        }
        public static XmlSerializer GetSerializer<T>(Type[] ExtraTypes)
        {
            return GetSerializer(typeof(T), ExtraTypes);
        }
        public static XmlSerializer GetSerializer(Type MainTypeForSerialization)
        {
            return GetSerializer(MainTypeForSerialization, null);
        }
        public static XmlSerializer GetSerializer(Type MainTypeForSerialization, Type[] ExtraTypes)
        {
            string Signature = MainTypeForSerialization.FullName;
            if (ExtraTypes != null)
            {
                foreach (Type Tp in ExtraTypes)
                    Signature += "-" + Tp.FullName;
            }
    
            XmlSerializer XmlEventSerializer;
            if (SerializerCacheForUtils.ContainsKey(Signature))
                XmlEventSerializer = SerializerCacheForUtils[Signature];
            else
            {
                if (ExtraTypes == null)
                    XmlEventSerializer = new XmlSerializer(MainTypeForSerialization);
                else
                    XmlEventSerializer = new XmlSerializer(MainTypeForSerialization, ExtraTypes);
    
                SerializerCacheForUtils.Add(Signature, XmlEventSerializer);
            }
            return XmlEventSerializer;
        }
    
        public static T Deserialize<T>(XDocument XmlData)
        {
            return Deserialize<T>(XmlData, null);
        }
        public static T Deserialize<T>(XDocument XmlData, Type[] ExtraTypes)
        {
            lock (Locker)
            {
                T Result = default(T);
                try
                {
                    XmlReader XmlReader = XmlData.Root.CreateReader();
                    XmlSerializer Ser = GetSerializer<T>(ExtraTypes);
                    Result = (T)Ser.Deserialize(XmlReader);
                    XmlReader.Dispose();
                    return Result;
                }
                catch (Exception Ex)
                {
                    throw new Exception("Could not deserialize to " + typeof(T).Name, Ex);
                }
            }
        }
        public static T Deserialize<T>(string XmlData)
        {
            return Deserialize<T>(XmlData, null);
        }
        public static T Deserialize<T>(string XmlData, Type[] ExtraTypes)
        {
            lock (Locker)
            {
                T Result = default(T);
                try
                {
    
                    using (MemoryStream Stream = new MemoryStream())
                    {
                        using (StreamWriter Writer = new StreamWriter(Stream))
                        {
                            Writer.Write(XmlData);
                            Writer.Flush();
                            Stream.Position = 0;
                            XmlSerializer Ser = GetSerializer<T>(ExtraTypes);
                            Result = (T)Ser.Deserialize(Stream);
                            Writer.Close();
                        }
                    }
                    return Result;
                }
                catch (Exception Ex)
                {
                    throw new Exception("Could not deserialize to " + typeof(T).Name, Ex);
                }
            }
        }
    
        public static XDocument Serialize<T>(T Object)
        {
            return Serialize<T>(Object, null);
        }
        public static XDocument Serialize<T>(T Object, Type[] ExtraTypes)
        {
            lock (Locker)
            {
                XDocument Xml = null;
                try
                {
                    using (MemoryStream stream = new MemoryStream())
                    {
                        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                        ns.Add("", "");
    
                        using (StreamReader Reader = new StreamReader(stream))
                        {
                            XmlSerializer Serializer = GetSerializer<T>(ExtraTypes);
                            var settings = new XmlWriterSettings { Indent = true };
                            using (var w = XmlWriter.Create(stream, settings))
                            {
                                Serializer.Serialize(w, Object, ns);
                                w.Flush();
                                stream.Position = 0;
                            }
                            Xml = XDocument.Load(Reader, LoadOptions.None);
    
                            foreach (XElement Ele in Xml.Root.Descendants())
                            {
                                PropertyInfo PI = typeof(T).GetProperty(Ele.Name.LocalName);
                                if (PI != null && PI.IsDefined(typeof(XmlCommentAttribute), false))
                                    Xml.AddFirst(new XComment(PI.Name + ": " + PI.GetCustomAttributes(typeof(XmlCommentAttribute), false).Cast<XmlCommentAttribute>().Single().Value));
                            }
    
                            Reader.Close();
                        }
                    }
                    return Xml;
                }
                catch (Exception Ex)
                {
                    throw new Exception("Could not serialize from " + typeof(T).Name + " to xml string", Ex);
                }
            }
        }
    }
    
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class XmlCommentAttribute : Attribute
    {
        public string Value { get; set; }
    }
    

    【讨论】:

      【解决方案5】:

      我最近在使用最新的 .NET Core 3.1 时遇到了同样的问题,缓存 XMLSerializer(在此处提出)起到了作用。这种内存泄漏的最糟糕的事情是无法从内存转储中明确定位,我尝试了 Jetbrains 的 dotMemory,根据分析转储的结果,一切似乎都很好,但是应用程序使用的内存量(转储的大小)并且 dotMemory 报告中显示的应用程序使用的内存量存在显着差异。 dotMemory 仅显示 APP 使用的内存只有几 MB。我最初认为问题是由 WCF 引起的,当合同(WSDL)使用与 utf-8 不同的编码时,特别是当合同在方法名称中包含点时(The服务器端是用 PHP 编写的)这对 .Net Framework 来说不是问题,但是 .Net Core 的工具是不同的。我不得不手动调整 WSDL 并添加 .Net Core 实现中缺少的一些类,以使其适用于不同的编码。

      【讨论】:

        【解决方案6】:

        我认为将 XMLSerializer 构造函数移出循环并缓存其结果将修复它,解释 here

        【讨论】:

        • 外循环还不够:应该缓存静态IMO
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-15
        相关资源
        最近更新 更多