使用StreamReader和XmlSerializer的内存泄漏

本文关键字:内存 泄漏 XmlSerializer StreamReader 使用 | 更新日期: 2023-09-27 18:05:20

我在过去的几个小时里一直在谷歌搜索,尝试不同的东西,但似乎无法找到这个....

当我运行这段代码时,内存使用持续增长。

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();但这似乎没有任何作用

使用StreamReader和XmlSerializer的内存泄漏

泄漏在这里:

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

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

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

首先,即使抛出异常(XMLObj也是如此),也应该处理StreamReader。使用using语句。当前,当抛出异常时,您将无法处理。

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

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

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

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

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

From MSDN:在这里输入链接描述

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

XmlSerializer.XmlSerializer(类型)

XmlSerializer.XmlSerializer(类型、字符串)

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

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

并在XMLObj类中添加根XML属性。

[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);
    }
}

我使用一个"缓存"类,以避免实例化xmlserializer每次你需要序列化的东西(还添加了一个XmlCommentAttribute添加评论到xml输出的序列化属性),对我来说,它的工作原理就像夏利,希望帮助有人与此:

 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; }
}

我最近在最新的。net Core 3.1中遇到了同样的问题,缓存XMLSerializer(此处建议)解决了这个问题。这个内存泄漏最糟糕的事情是不能从内存转储中清楚地定位,我尝试了Jetbrains的dotMemory,根据分析转储的结果,一切似乎都很好,但是应用程序使用的内存量(转储的大小)和dotMemory报告中显示的应用程序使用的内存量显着不同。dotMemory显示只有几MB的内存被应用程序使用。我最初认为这个问题是由WCF引起的,当合同(WSDL)使用不同于utf-8的编码时,特别是当合同包含方法名称中的点(服务器端是用PHP编写的),这将不是。net框架的问题,但。net Core的工具不同。我不得不手动调整WSDL,并添加一些。net Core实现中缺少的类,以便为不同的编码工作。

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