使用 StreamReader 和 XmlSerializer 的内存泄漏

Memory Leak using StreamReader and XmlSerializer

提问人:Alex999 提问时间:5/28/2014 最后编辑:John SaundersAlex999 更新时间:8/7/2020 访问量:26542

问:

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

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

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。收集();但这似乎什么也做不了。

C# XML StreamReader XmlSerializer

评论

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

答:

10赞 Eric J. 5/28/2014 #1

首先,即使抛出异常,也应该处理 StreamReader(XMLObj 也是如此)。使用语句。目前,在引发异常时不会释放。using

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

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

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

更新

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

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

评论

1赞 Alex999 5/28/2014
这奏效了!多谢。这是 XmlSerializer 的构造函数在循环中的问题。一旦取出,内存是稳定的。
1赞 Peter Wishart 5/28/2014 #2

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

评论

5赞 Marc Gravell 5/28/2014
循环之外是不够的:它应该缓存静态IMO
98赞 Marc Gravell 5/28/2014 #3

泄漏就在这里:

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

XmlSerializer使用程序集生成,并且无法收集程序集。它为最简单的构造函数方案(等)执行一些自动缓存/重用,但不适用于此方案。因此,您应该手动缓存它:new XmlSerializer(Type)

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

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

评论

0赞 Lionia Vasilev 11/12/2015
只有某些构造函数使用缓存的背后是否有任何逻辑?
1赞 Marc Gravell 11/12/2015
@LeonidVasilyev可能方便;缓存单个场景的东西很容易;要执行更复杂的方案,将涉及构建哈希算法和强大的相等性检查来比较不同的配置,以便与某些类似的哈希表一起使用
3赞 Dan 2/11/2016
那行毫无戒心的代码也导致了我的应用程序中的内存泄漏;我已经确认了。很棒的发现!
0赞 Shantu 3/15/2019
如果处于 foreach 循环中,请在循环之前初始化它 (XmlSerializer mySerializer = new ..),并将对象 (mySerializer ) 传递给用于反序列化的方法
1赞 Mertus 2/23/2021
一如既往,马克·格拉维尔(Marc Gravel)前来救援:)我已经在内存转储中挖掘了几个小时。
9赞 Alex Nguyen 3/21/2016 #4

来自 MSDN:在此处输入链接描述

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

XmlSerializer.XmlSerializer (类型)

XmlSerializer.XmlSerializer (类型, 字符串)

如果使用任何其他构造函数,则会生成同一程序集的多个版本,并且永远不会卸载,这会导致内存泄漏和性能不佳。最简单的解决方案是使用前面提到的两个构造函数之一。否则,必须将程序集缓存在 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);
    }
}
5赞 Danilow 2/14/2017 #5

我正在使用一个“缓存”类来避免每次需要序列化某些内容时实例化 xmlserializer(还添加了一个 XmlCommentAttribute,用于向 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; }
}
2赞 Martin Rulf 8/7/2020 #6

我最近在最新的 .NET Core 3.1 中遇到了同样的问题,缓存 XMLSerializer(此处建议)解决了问题。这种内存泄漏最糟糕的事情是无法从内存转储中清楚地定位,我尝试了 Jetbrains 的 dotMemory,根据分析转储的结果,一切似乎都正常,但应用程序使用的内存量(转储大小)和应用程序使用的内存量在 dotMemory 报告中显示显着不同。dotMemory 显示 APP 只使用了几 MB 的内存。我最初认为这个问题是由 WCF 引起的,当合约 (WSDL) 使用与 utf-8 不同的编码时,尤其是当合约在方法名称中包含点时,这真的很棘手 .Net Framework 不会有问题, 但 .Net Core 的工具是不同的。我不得不手动调整 WSDL 并添加一些 .Net Core 实现中缺少的类,以便为不同的编码工作。