【问题标题】:"Exception of type 'System.OutOfMemoryException' was thrown" while using xmlserializer使用 xmlserializer 时,“抛出了 'System.OutOfMemoryException' 类型的异常”
【发布时间】:2012-01-25 07:29:16
【问题描述】:

我正在使用下面的代码来获取一个 xml 字符串。

public static string ToXMLString(object obj, string nodeName)
{
    XmlSerializer xmlSerializer = default(XmlSerializer);
    string xml = string.Empty;
    StreamReader r = default(StreamReader);
    try
    {
        if (obj != null)
        {
            using (MemoryStream m = new MemoryStream())
            {
                using (XmlWriter writer = XmlWriter.Create(m, new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true }))
                {
                    // Don't include XML namespace
                    XmlSerializerNamespaces xmlnsEmpty = new XmlSerializerNamespaces();
                    xmlnsEmpty.Add("", "");
                    if (xmlSerializer == null)
                        xmlSerializer = new XmlSerializer(obj.GetType(), new XmlRootAttribute(nodeName));
                    xmlSerializer.Serialize(writer, obj, xmlnsEmpty);

                    m.Flush();
                    m.Position = 0;

                    r = new StreamReader(m);
                    xml = r.ReadToEnd();
                    xmlSerializer = null;
                }
            }
        }

        return xml;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }
    finally
    {
        r.Close();
        r.Dispose();
    }
    //XmlSerializer xmlSerializer;

}

我有一个使用该方法运行的循环,一段时间后我得到一个内存不足异常,如下所示:

异常的原因可能是什么? using 语句真的在处理流吗?或者我可以使用其他什么替代方法?

【问题讨论】:

  • 它不会影响代码,但是在顶部声明这些变量没有好处,也没有在最后将xmlSerializer分配给null;我只是在线声明,即var xmlSerializer = new XmlSerializer(...);。你没有处理读者,但这不会杀死你。实际上,如果您只想要字符串,您可以写信给StringBuilder - 避免弄乱Stream...
  • 我们可以看到您正在序列化的对象模型吗?我怀疑这与模型有关 - 类似于循环引用(即逃避内置检测 - 并非不可能),或类似的东西
  • using 在退出 using 块时调用 IDisposable.Dispose()。所以你的那部分代码没问题。
  • 这与OOM错误无关,但是:这是相同代码的稍微重构和更直接的版本:pastie.org/3248365
  • 想知道你为什么在这里两次使用default(StreamReader) 语法?此外,您正在覆盖 r 而不处置先前的实例。无论如何它都会超出范围并被 GC 处理,但不会像显式处理那样快。

标签: c# .net


【解决方案1】:

我预计这里的问题是装配饱和。 XmlSerializer 通过动态生成程序集来工作;如果您使用 XmlSerializer(Type) 构造函数,它会缓存并查找它;但对于 任何其他构造函数,它不会。并且程序集不能(通常)通过卸载。所以你会得到越来越多的程序集吞噬你的记忆。如果您在循环中运行,则需要缓存序列化程序:

using System;
using System.Collections;
using System.IO;
using System.Xml;
using System.Xml.Serialization;


public static class Program
{
    static void Main()
    {
        // the loop here is from your comment
        for (int i = 0; i < 10000000; i++) { ToXMLString("test", string.Format("test")); Console.WriteLine(i); }
    }

    // why is this Hashtable? due to the threading semantics!
    private static readonly Hashtable serializerCache = new Hashtable();

    public static string ToXMLString(object obj, string nodeName)
    {
        if (obj == null) throw new ArgumentNullException("obj");
        Type type = obj.GetType();
        var cacheKey = new { Type = type, Name = nodeName };
        XmlSerializer xmlSerializer = (XmlSerializer)serializerCache[cacheKey];
        if (xmlSerializer == null)
        {
            lock (serializerCache)
            { // double-checked
                xmlSerializer = (XmlSerializer)serializerCache[cacheKey];
                if (xmlSerializer == null)
                {
                    xmlSerializer = new XmlSerializer(type, new XmlRootAttribute(nodeName));
                    serializerCache.Add(cacheKey, xmlSerializer);
                }
            }
        }
        try
        {

            StringWriter sw = new StringWriter();
            using (XmlWriter writer = XmlWriter.Create(sw,
                new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true }))
            {
                // Don't include XML namespace
                XmlSerializerNamespaces xmlnsEmpty = new XmlSerializerNamespaces();
                xmlnsEmpty.Add("", "");
                xmlSerializer.Serialize(writer, obj, xmlnsEmpty);
            }
            return sw.ToString();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex.Message);
            throw;
        }
    }
}

【讨论】:

  • for (int i = 0; i
  • @user1010863 我不知道如何解释该评论... - 另外,string.Format("test") - 只是.... 为什么?
  • 这就是我如何使用我的应用程序中的方法。我仍然很困惑什么可能会大量使用内存。它是一个运行调用函数的循环。查看任务管理器,devenv 的物理内存不断增加到大约 83%,我得到了异常。
  • @user1010863 是那个吗?还是没有?我建议的程序集缓存?
  • 现在你建议的缓存,它的行为是一样的
【解决方案2】:

这里的问题可能不是这个代码本身,而是你在这个方法之外使用生成的字符串做什么。

根据您要序列化的内容,这可能会产生许多大字符串。如果您在循环时抓住这些字符串,您很可能会消耗越来越多的内存。更糟糕的是,即使 absolute 使用的内存量可能不是很大,这些大字符串很可能会导致内存碎片 - GC 可能无法分配连续块下一个字符串的内存。

在 CLR 中 - 大型对象(我认为大约 85KB 左右)不会分配给通常的 GC 代;相反,它们进入大对象堆。这个堆永远不会被压缩(除非这在.Net 4中发生了变化,在这种情况下我可能不知道)。这意味着如果你有很多字符串被分配并被保留,那么最终会有越来越少的连续可用空间块足够大来分配你的下一个字符串这是因为没有进程来压缩分配的当其他内存块被释放时一起阻塞。如上所述,在执行此类操作时,这很容易导致内存不足异常。

This 文章很好地概述了大对象堆的“危险”和注意事项。

你对这个方法返回的字符串做了什么,生成的字符串有多大?

【讨论】:

  • 根据我的阅读,大型对象堆现在使用 .Net 4.5(及更高版本)进行压缩
猜你喜欢
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
  • 2013-01-14
  • 2013-08-28
相关资源
最近更新 更多