反序列化字典上的 c# 内存泄漏

c# memory leak on Deserialize dictionary

当我使用该方法反序列化字典时,我发生了内存泄漏。此处进行以下测试以重现问题。

public static Dictionary<TKey, TValue> DeserializeDictionary<TKey, TValue>(this string iSerialization)
    {
        Dictionary<TKey, TValue> dic;

        using (var textWriter = new StringReader(iSerialization))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Item<TKey, TValue>[]), new XmlRootAttribute() { ElementName = "items" });
            dic = ((Item<TKey, TValue>[])serializer.Deserialize(textWriter)).ToDictionary(i => i.Key, i => i.Value);
            textWriter.Close();
        }
        return dic;
    }

public class Item<TKey, TValue>
    {
        [XmlAttribute]
        public TKey Key;

        [XmlAttribute]
        public TValue Value;
    }

测试:

[TestMethod]
    public void test()
    {
        string test = "<?xml version=\"1.0\" encoding=\"utf-16\"?><items xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><ItemOfStringString Key=\"mykey\" Value=\"myvalue\" /></items>";

        while(true)
        {
            Dictionary<string, string> tesfezf = test.DeserializeDictionary<string, string>();
        }

    }

你知道问题出在哪里吗?

编辑:我在循环中(大约 20000)在 workerazure 角色中使用此方法,这会填充内存并抛出内存不足异常。

与反序列化无关,与XmlSerializer的实例化无关。这个特定的构造函数重载会生成一个新的临时程序集,每次调用时都会将其加载到应用程序域中,并且永远不会卸载。最终结果是您应该缓存 XmlSerializer 或使用不同的构造函数,如果您计划在应用程序域的生命周期中多次使用它。

你可以用这段代码测试一下,你会发现你的内存使用量增加了。

var root = new XmlRootAttribute() {ElementName = "items"};
var type = typeof (Item<string, string>[]);
while (true)
{
    XmlSerializer serializer = new XmlSerializer(type, root);
    GC.Collect();
}

如果您将块 XmlSerializer serializer = new XmlSerializer(type, root); 移出代码中的循环并在循环中反序列化,内存将保持不变。

这里有很多文章都描述了相同的问题,包括 Microsoft 网站上的支持文章。

Microsoft Support KB 886385

Blog on Msdn - .NET Memory Leak: XmlSerializing your way to a Memory Leak

A Memory Leak brought to you by XmlSerializer

Question and Answer on Whosebug

据我所知,没有内存泄漏。但是在你的测试用例中有一个明显的错误内存使用。

CG.Collect() 大多数时候并不是迫在眉睫。特别是当有非托管资源时,它必须在释放所有不需要的内存之前清除终结队列。

在这种情况下,它会消耗大量内存并且不允许垃圾收集器完成其终结过程。

因此,反序列化后,您可以调用 GC.Collect() 并等待 GC 完成其最终确定。

   //Force garbage collection.
   GC.Collect();

   // Wait for all finalizers to complete before continuing. 
   // Without this call to GC.WaitForPendingFinalizers,  
   // the worker loop below might execute at the same time  
   // as the finalizers. 
   // With this call, the worker loop executes only after 
   // all finalizers have been called.
   GC.WaitForPendingFinalizers();

参考:

GC.WaitForPendingFinalizers

GC.WaitForFullGCComplete Method