XDocument + IEnumerable导致System.Xml.Linq.dll中出现内存不足异常

时间:2010-12-15 16:51:10

标签: c# linq garbage-collection linq-to-xml out-of-memory

基本上我有一个程序,当它开始加载文件列表(如FileInfo)时,对于列表中的每个文件,它都会加载一个XML文档(如XDocument)。

程序然后将数据从中读取到容器类中(存储为IEnumerables),此时XDocument超出范围。

然后程序将数据从容器类导出到数据库。在导出之后,容器类超出了范围,但是,垃圾收集器没有清除容器类,因为它存储为IEnumerable,似乎导致XDocument留在内存中(不确定这是否是原因,但任务管理器显示XDocument的内存未被释放。

当程序循环遍历多个文件时,程序最终会抛出一个内存不足的异常。为了减轻这种情况,我最终使用了

System.GC.Collect(); 

在容器超出范围后强制垃圾收集器运行。这是有效的,但我的问题是:

  • 这是正确的做法吗? (强制垃圾收集器运行似乎有点奇怪)
  • 有没有更好的方法来确保处理XDocument内存?
  • 除IEnumerable之外,是否有其他原因导致文档内存未被释放?

感谢。


修改:代码示例:

  • 容器类:

    public IEnumerable<CustomClassOne> CustomClassOne { get; set; }
    public IEnumerable<CustomClassTwo> CustomClassTwo { get; set; }
    public IEnumerable<CustomClassThree> CustomClassThree { get; set; }
    ...
    public IEnumerable<CustomClassNine> CustomClassNine { get; set; }
    
  • 自定义类:

    public long VariableOne { get; set; }
    public int VariableTwo { get; set; }
    public DateTime VariableThree { get; set; }
    ...
    

    无论如何,这确实是基本结构。自定义类通过XML文档中的容器类填充。填充的结构本身使用的内存非常少。

从一个XML文档填充容器类,超出范围,然后加载下一个文档,例如

    public static void ExportAll(IEnumerable<FileInfo> files)
    {
        foreach (FileInfo file in files)
        {
            ExportFile(file);
            //Temporary to clear memory
            System.GC.Collect();
        }
    }
    private static void ExportFile(FileInfo file)
    {
        ContainerClass containerClass = Reader.ReadXMLDocument(file);
        ExportContainerClass(containerClass);
        //Export simply dumps the data from the container class into a database
        //Container Class (and any passed container classes) goes out of scope at end of export
    }

    public static ContainerClass ReadXMLDocument(FileInfo fileToRead)
    {
        XDocument document = GetXDocument(fileToRead);
        var containerClass = new ContainerClass();

        //ForEach customClass in containerClass
        //Read all data for customClass from XDocument

        return containerClass;
    }

忘了提这个位(不确定它是否相关),文件可以压缩为.gz所以我有GetXDocument()方法来加载它

    private static XDocument GetXDocument(FileInfo fileToRead)
    {
        XDocument document;

        using (FileStream fileStream = new FileStream(fileToRead.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            if (String.Equals(fileToRead.Extension, ".gz", StringComparison.OrdinalIgnoreCase))
            {
                using (GZipStream zipStream = new GZipStream(fileStream, CompressionMode.Decompress))
                {
                    document = XDocument.Load(zipStream);
                }
            }
            else
            {
                document = XDocument.Load(fileStream);
            }
            return document;
        }
    }

希望这是足够的信息。 感谢

修改System.GC.Collect() 100%无法正常工作,有时程序似乎保留XDocument,任何人都知道为什么会这样?< / p>

public static ContainerClass ReadXMLDocument(FileInfo fileToRead)
{
    XDocument document = GetXDocument(fileToRead);
    var containerClass = new ContainerClass();

    //ForEach customClass in containerClass
    //Read all data for customClass from XDocument

    containerClass.CustomClassOne = document.Descendants(ElementName)
        .DescendantsAndSelf(ElementChildName)
        .Select(a => ExtractDetails(a));

    return containerClass;
}

private static CustomClassOne ExtractDetails(XElement itemElement)
{
    var customClassOne = new CustomClassOne();
    customClassOne.VariableOne = Int64.Parse(itemElement.Attribute("id").Value.Substring(4));
    customClassOne.VariableTwo = int.Parse(itemElement.Element(osgb + "version").Value);
    customClassOne.VariableThree = DateTime.ParseExact(itemElement.Element(osgb + "versionDate").Value,
            "yyyy-MM-dd", CultureInfo.InvariantCulture);
    return customClassOne;
}

8 个答案:

答案 0 :(得分:9)

在某些情况下,强制手动垃圾收集似乎已经解决了你的问题,但是可以肯定的是,这并不比巧合更好。

您需要做的是停止猜测导致内存压力问题的原因,并找出确定的问题。

我在类似的情况下使用了JetBrains dotTrace非常好的效果 - 设置断点,触发探查器并浏览所有“实时”对象及其关系的视图。可以很容易地找到哪些对象仍然保留,以及它们保存的引用。

虽然我自己没有使用过,但许多人也推荐使用RedGate Ants Memory Profiler

这两种工具都有免费试用,这应该足以解决您当前的问题。虽然,我强烈建议买一个或者另一个 - dotTrace为我节省了数十小时的内存问题,非常值得投资。

答案 1 :(得分:5)

您的代码对我来说并不坏看,我也没有看到任何强迫收集的原因。如果您的自定义类包含对XDocument的XElements的引用,那么GC将不会既不收集它们也不收集文档本身。如果其他东西持有对你的枚举的引用,那么它们也不会被收集。所以我真的很想看到你的自定义类定义及其填充方式。

答案 2 :(得分:2)

您对调用GC.Collect的态度是正确的。需要调用此方法表明您的代码存在其他问题。

然而,你的陈述有一些让我觉得你对记忆的理解有点偏差。任务管理器是一个非常差的指标,表明你的程序实际使用了多少内存;分析器对于此任务要好得多。就内存而言,如果可以收集,GC将在需要时收集内存。

虽然这是一个措辞细节,但你会问“如何确保处理XDocument内存”。 Disposed 通常用于指代手动释放非托管资源,例如数据库连接或文件句柄; GC 收集内存。

现在试着回答实际的问题。引用您不释放的对象非常容易,尤其是在使用lambdas和LINQ时。键入IEnumerable的内容特别容易出现这种情况,因为懒惰评估的LINQ函数几乎总是会引入您认为未使用的对象的引用。您省略的ReadXMLDocument代码可能是开始查找的好地方。

另一种可能性是TomTom所建议的,你正在使用的数据库类可能存储了你自己没有想到的对象。

答案 3 :(得分:2)

如果处理的XML文件太大(大约500-800M),则不能使用XDocument(或XmlDocument),因为它会尝试将整个文档加载到内存中。请参阅此讨论:Does LINQ handle large XML files? Getting OutOfMemoryException

在这种情况下,您应该使用XStreamingElement Class并从中构建ContainerClass。

也许去64位进程会有所帮助,但最佳做法是始终使用端到端的流式传输。

答案 4 :(得分:2)

这不是真正的答案,更多的调查建议:如果GC.Collect没有帮助,那意味着你仍然在某处保留对象的引用。 寻找可能保持参考的单身人士和缓存。

如果您真实地获得异常或者可以收集内存转储,您可以使用WinDbg + Sos查找谁拥有对象的引用:搜索“memory leaks sos windbg”以查找详细信息。

答案 5 :(得分:2)

无论如何,请使用

String.Equals(fileToRead.Extension, ".gz", StringComparison.OrdinalIgnoreCase)

代替

String.Compare()

答案 6 :(得分:0)

您可以尝试使用tolist强制进行评估:

public static ContainerClass ReadXMLDocument(FileInfo fileToRead) 
{     
  XDocument document = GetXDocument(fileToRead);     
  var containerClass = new ContainerClass();      
  //ForEach customClass in containerClass     
  //Read all data for customClass from XDocument      
  containerClass.CustomClassOne = document.Descendants(ElementName)
    .DescendantsAndSelf(ElementChildName)         
    .Select(a => ExtractDetails(a)).ToList();      
  return containerClass; 
} 

答案 7 :(得分:-1)

  

程序然后从中导出数据   容器类到数据库。   导出容器类之后   然而,超出了范围   垃圾收集器没有清理   容器类,因为它   存储为IEnumerable,似乎领先   留在记忆中的XDocument   (不确定这是否是原因,但是   任务管理器正在显示内存   来自XDocument没有被释放)。

原因是LYNC存储了每个项目读取它自己的事务参考池。 Basiaclly它这样做,因此重读它可以使项目独特。

建议:

  • 仅将基本键加载到数组中。提交。

  • 一次一个地遍历列表和处理项目,每个项目后都会提交。