XDocument + IEnumerable вызывает исключение нехватки памяти в System.Xml.Linq.dll - PullRequest
13 голосов
/ 15 декабря 2010

В основном у меня есть программа, которая при запуске загружает список файлов (как 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, у кого-нибудь есть идеи, почему это может быть?

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 ]

9 голосов
/ 10 января 2011

В некоторых случаях принудительное выполнение сборки мусора могло бы решить вашу проблему, но можно с уверенностью сказать, что это не что иное, как совпадение.

Что вам нужно сделать, это перестать угадывать о том, что вызывает проблемы с памятью, и вместо этого выяснить наверняка.

Я использовал JetBrains dotTrace для очень хорошего эффекта в подобных ситуациях - установите точку останова, запустите профилировщик и просмотрите все «живые» объекты и их отношения. Позволяет легко находить, какие объекты все еще сохраняются и по каким ссылкам они сохраняются.

Хотя я сам этим не пользовался, многим также рекомендуется RedGate Ants Memory Profiler .

Оба эти инструмента имеют бесплатные пробные версии, которых должно быть достаточно для решения вашей текущей проблемы. Тем не менее, я настоятельно рекомендую, чтобы стоило купить одно или другое - dotTrace сэкономил мне десятки часов на устранение проблем с памятью, что окупается очень высоко.

5 голосов
/ 11 января 2011

Ваш код не выглядит для меня плохо, и я не вижу ни единой причины для принудительного сбора. Если ваш пользовательский класс содержит ссылку на XElements из XDocument, то GC не будет собирать ни их, ни сам документ. Если что-то еще содержит ссылки на ваши перечисляемые элементы, они также не будут собраны. Поэтому я бы очень хотел увидеть ваше определение класса и его заполнение.

2 голосов
/ 10 января 2011

В любом случае, используйте

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

вместо

String.Compare()
2 голосов
/ 08 января 2011

На самом деле это не ответ, а более подробное исследование: если GC.Collect не помогает, это в значительной степени означает, что вы по-прежнему сохраняете где-то ссылки на объекты. Ищите синглтоны и тайники, которые могут сохранять ссылки.

Если вы действительно получили исключение или можете собрать дамп памяти, вы можете использовать WinDbg + Sos, чтобы найти тех, кто хранит ссылки на объекты: выполните поиск по запросу «утечки памяти sos windbg», чтобы найти подробности.

2 голосов
/ 05 января 2011

Если обработанные файлы XML слишком велики (около 500-800M), вы не можете использовать XDocument (или XmlDocument), потому что он попытается загрузить весь документ в память. Смотрите это обсуждение: Обрабатывает ли LINQ большие XML-файлы? Получение OutOfMemoryException

В этом случае вам лучше использовать XStreamingElement Class и создать из него свой ContainerClass.

Возможно, поможет переход на 64-битный процесс, но лучше всего всегда использовать потоковую передачу из конца в конец.

2 голосов
/ 04 января 2011

Вы склонны звонить GC.Collect правильно.Необходимость вызова этого метода является признаком того, что с вашим кодом что-то не так.

Однако в ваших утверждениях есть несколько моментов, которые заставляют меня думать, что ваше понимание памяти немного неправильное.Диспетчер задач - очень плохой показатель того, сколько памяти фактически использует ваша программа;профилировщик намного лучше для этой задачи.Что касается памяти, если она может быть собрана, GC будет собирать память, когда это необходимо.

Хотя это немного детализация формулировки, вы спрашиваете, как «убедиться, что память XDocument утилизируется». Disposed обычно используется для обозначения ручного освобождения неуправляемых ресурсов, таких как соединения с базой данных или файловые дескрипторы;GC собирает памяти.

Теперь попробуем ответить на реальный вопрос.Очень легко иметь ссылки на объекты, которые вы не освобождаете, особенно при использовании lambdas и LINQ.Вещи, напечатанные как IEnumerable, особенно подвержены этому, поскольку лениво оцененные функции LINQ почти всегда вводят ссылки на объекты, которые, по вашему мнению, в противном случае не используются.Код ReadXMLDocument, который вы пропустили, может быть хорошим началом для поиска.

Другая возможность - это то, что предложено TomTom в том, что используемые вами классы базы данных могут хранить объекты, которые вы не делали.ожидать по своим собственным причинам.

0 голосов
/ 11 января 2011

Вы можете попытаться форсировать оценку с помощью 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; 
} 
0 голосов
/ 15 декабря 2010

Затем программа экспортирует данные из класс контейнера для базы данных. После экспорта контейнер класса выходит за рамки, однако, сборщик мусора не очищается класс контейнера, который, потому что его хранение как IEnumerable, кажется, ведет на XDocument, оставаясь в памяти (Не уверен, что это причина, но диспетчер задач показывает память из XDocument не освобождается).

Причина в том, что LYNC хранила КАЖДОГО ЧТЕНИЯ в своем собственном пуле ссылок для транзакции. По сути, это так, что при повторном прочтении он может уникально воспринять предмет.

Предложение:

  • Загружать в массив только первичные ключи. COMMIT.

  • Просматривайте список и обрабатывайте элементы по одному, совершая коммиты после каждого.

...