web-dev-qa-db-ja.com

StreamReaderとXmlSerializerを使用したメモリリーク

私は過去数時間グーグルしていて、さまざまなことを試していましたが、これの底には見えません...

このコードを実行すると、メモリ使用量が増加し続けます。

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();に追加してみました。しかし、それは何もしないようです。

25
Alex999

リークはここにあります:

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

XmlSerializerはアセンブリ生成を使用しており、アセンブリを収集できません。 simplestコンストラクターシナリオ(new XmlSerializer(Type)など)に対していくつかの自動キャッシュ/再利用を行いますが、not =このシナリオの場合。したがって、手動でキャッシュする必要があります。

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

キャッシュされたシリアライザインスタンスを使用します。

60
Marc Gravell

まず、例外がスローされた場合でも、StreamReaderを破棄する必要があります(XMLObjと同じ)。 usingステートメントを使用します。現在、例外がスローされた場合は破棄しません。

メモリリークが発生することはほとんどありません。おそらく、ランタイムはまだメモリを収集することを選択していません。 GC.Collectであっても、必ずしもメモリが解放されるとは限りません。

非常に大きなXMLファイル(マルチGB)を処理するときに、同様の状況に遭遇しました。ランタイムは使用可能なメモリの大部分を取得しますが、メモリの圧力が必要な場合は解放します。

Visual Studioのメモリプロファイラーを使用して、割り当てられているメモリと、メモリがどの世代に存在するかを確認できます。

[〜#〜]更新[〜#〜]

@KaiEichingerからのコメントは調査に値します。 XmlSerializerがループの繰り返しごとに新しいキャッシュオブジェクト定義を作成している可能性があることを示しています

XMLSerializerコンストラクターは、リフレクションを使用してシリアル化される型の一時的なアセンブリを作成します。コード生成にはコストがかかるため、アセンブリは型ごとにメモリにキャッシュされます。しかし、ルート名は何度も変更され、動的になる可能性があり、動的アセンブリをキャッシュしません。したがって、上記のコード行が呼び出されるたびに、新しいアセンブリが読み込まれ、AppDomainがアンロードされるまでメモリに残ります。

9
Eric J.

MSDNから: ここにリンクの説明を入力してください

パフォーマンスを向上させるために、XMLシリアル化インフラストラクチャは、指定された型をシリアル化および逆シリアル化するアセンブリを動的に生成します。インフラストラクチャはこれらのアセンブリを見つけて再利用します。この現象は、次のコンストラクターを使用する場合にのみ発生します。

XmlSerializer.XmlSerializer(Type)

XmlSerializer.XmlSerializer(Type、String)

他のコンストラクタを使用すると、同じアセンブリの複数のバージョンが生成され、アンロードされないため、メモリリークが発生し、パフォーマンスが低下します。最も簡単な解決策は、前述の2つのコンストラクターの1つを使用することです。それ以外の場合は、次の例に示すように、アセンブリを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
Alex Nguyen

「キャッシュ」クラスを使用して、何かをシリアル化する必要があるたびにxmlserializerのインスタンス化を回避します(また、xml出力のシリアル化されたプロパティにコメントを追加するためのXmlCommentAttributeを追加しました)。 :

 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
Danilow

XMLSerializerコンストラクターをループの外に移動してその結果をキャッシュすると、修正されると思います。説明 here

0
Peter Wishart