Кучи .NET, заполненные строковым объектом -> OutOfMemoryException - PullRequest
1 голос
/ 19 февраля 2012

Я постоянно (каждые 30-60 минут) получаю исключение System.OutOfMemoryException в моей службе Windows. Работа сервиса состоит в том, чтобы зациклить 6 каталогов, которые содержат файлы данных, которые сервисные данные моют в общий формат данных XML.

Эти 6 папок содержат 5-10 000 файлов в каждой, поэтому общее количество файлов составляет около 45 000, и новые файлы добавляются в течение дня. Там добавляется около 1-2000 новых файлов в день. Размер файлов составляет от 4 до 500 КБ.

Каждый файл данных стирается в общий формат данных XML через объект XElement.

Я использовал RedGates ANTS Memory Profiler в службе, и объекты, которые используют больше всего памяти, - это строка (около 90 000 000 байт) и XElement (около 51 000 000 байт).

В Memory Profiler, когда я отслеживаю, что использует строковый объект, я вижу, что это в основном (93%) объект XElement, который использует строковый объект.

Сервер имеет 6 процессоров и 6 ГБ оперативной памяти, поэтому я не понимаю, почему я получаю исключение OutOfMemoryException. Если я посмотрю на службу Windows в процессах, то ее МАКСИМАЛЬНОЕ использование ОЗУ составило 1,2 ГБ.

Я читал, что сборщик мусора в .NET не очищает строковый объект, потому что строковый объект хранится в внутренней таблице. Может ли это быть ошибкой, если да, что я могу с этим поделать?

Код ниже показывает, как я перебираю файлы. Как вы можете видеть, я также пытался взять 20 файлов одновременно. Это просто выдвигает OutOfMemoryException на несколько часов, поэтому служба будет работать в течение 4-5 часов вместо 30-60 минут.

Почему я могу использовать OutOfMemoryException?

private static void CheckExistingImportFiles(object sender, System.Timers.ElapsedEventArgs e)
    {
        CheckTimer.Stop();
        var dir = Directory.GetFiles(RawDataDirectory.FullName, "*.*", SearchOption.AllDirectories);

        List<ManualResetEvent> doneEvents = new List<ManualResetEvent>();
        int i = 0;
        //int doNumberOfFiles = 20;

        foreach (string existingFile in Directory.GetFiles(RawDataDirectory.FullName, "*.*", SearchOption.AllDirectories))
        {
            if (existingFile.EndsWith("ignored") || existingFile.EndsWith("error") || existingFile.EndsWith("importing"))
            {
                //if (DateTime.UtcNow.Subtract(File.GetCreationTimeUtc(existingFile)).TotalDays > 5)
                //  File.Delete(existingFile);
                //continue;
            }

            StringBuilder fullFileName = new StringBuilder().Append(existingFile);

            if (!fullFileName.ToString().ToLower().EndsWith("error") && !fullFileName.ToString().ToLower().EndsWith("ignored") && !fullFileName.ToString().ToLower().EndsWith("importing"))
            {
                File.Move(fullFileName.ToString(), fullFileName + ".importing");
                fullFileName = fullFileName.Append(".importing");

                ImportFileJob newJob = new ImportFileJob(fullFileName.ToString());

                doneEvents.Add(new ManualResetEvent(false));

                ThreadPool.QueueUserWorkItem(newJob.Run, doneEvents.ElementAt(i));
                i++;
            }

            //if (i > doNumberOfFiles)
            //{
            //    i = 0;
            //    doNumberOfFiles = 20;
            //    break;
            //}
        }
        i = 0;
        WaitHandle.WaitAll(doneEvents.ToArray());

        CheckTimer.Start();
    }

Ответы [ 6 ]

2 голосов
/ 19 февраля 2012
Directory.GetFiles(RawDataDirectory.FullName, "*.*", SearchOption.AllDirectories);

Возвращает массив.Если в каталогах столько файлов, сколько вы указали, это будут очень большие массивы, достаточно большие для размещения в куче больших объектов.Mutliple массивные массивы могут легко вызвать исключение OutOfMemoryException.Не помогает, что следующая строка

var dir = Directory.GetFiles(RawDataDirectory.FullName, "*.*", SearchOption.AllDirectories);

имеет переменную 'dir', которая ничего не делает.Большой массив создается дважды за выполнение метода.

1 голос
/ 19 февраля 2012

Вместо использования таймера и циклического перебора всего содержимого папок вы можете использовать FileSystemWatcher: http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx

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

1 голос
/ 19 февраля 2012

Как уже говорил Авнер Шахар-Каштан, я также думаю, что проблема в ImportJob (вы не показали нам его код).

Несмотря на это, вы все равно можете сделать некоторые оптимизации.

Вам не нужно загружать все имена файлов одновременно.Это можно сделать dir с помощью dir, как показано ниже:

IEnumerable<string> GetAllFiles(string dirName)
{
    var dirs = Directory.GetDirectories(dirName);

    foreach (var file in Directory.GetFiles(dirName))
        yield return file;

    foreach (var dir in dirs) //recurse
        foreach (var file in GetAllFiles(dir)) 
            yield return file;
}

. Используя TPL, вы можете уменьшить количество созданных ManualResetEvent с (и их забытых Dispose() с)

Parallel.ForEach(GetAllFiles(RawDataDirectory.FullName) , file =>
{
    //ImportFileJob newJob = new ImportFileJob(file);
    //newJob.Run
    Console.WriteLine(file);
}); 

Кстати, вы также должны увидеть CountdownEvent

1 голос
/ 19 февраля 2012

Я могу сразу заметить пару простых оптимизаций.

Вы используете много звонков fullFileName.ToString().ToLower().EndsWith("ignored"). У них много накладных расходов, поскольку вы всегда берете данную строку и создаете новую строчную строку.

Вместо этого следует использовать перегрузки Endswith (или Contains), которые допускают сравнение без учета регистра:

fullFileName.ToString()
  .EndsWith("ignored", StringComparison.CurrentCultureIgnoreCase)

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

0 голосов
/ 19 февраля 2012

Как и предполагали другие,

1) уменьшить манипуляции со строками.

Похоже, ваш каталог возвращает "слишком много" имен файлов (строк), поэтому на это нужно обратить внимание.

2) ваша строка 'var dir = Directory.GetFiles(RawDataDirectory.FullName, "*.*", SearchOption.AllDirectories);' кажется излишним. Похоже, вы не используете его. Итак, удалите этот код, он содержит много строковых ссылок.

3) Если возможно, перебирайте файлы, возвращенные из каталога, кусками (скажем, 10K). Так что для этого потребуется написать код, который разделяет List to List>, а затем очистить ссылки, хранящиеся во внутреннем списке, когда вы выполняете итерацию по внешнему циклу. Что-то вроде

foreach(List<List<string>> fileNamesInChunk in GetFilesInChunk(directoryName)){
     foreach(var fileName in fileNamesInChunk){
     //Do the processing.
     }
     fileNamesInChunk.Clear(); //This would reduce the working set as you proceed.
}

Надеюсь, это поможет.

0 голосов
/ 19 февраля 2012

Вы вызываете fullFileName.ToString (). ToLower () три раза в своем операторе If. Кэшируйте это строковое значение в локальной переменной и используйте его в своем операторе if (сохраняет три временных строки).

Попробуйте использовать XmlWriter, а не XDocument. XDocument является графом объектов в памяти, поэтому для больших наборов данных он может быть не самым производительным (вы сохраняете все это в памяти, пока не записываете его на диск в целом). С XmlWriter вы, как правило, можете выполнять потоковую передачу в буферный элемент файла за элементом, объем памяти будет гораздо менее требовательным.

Не уверен, сколько работы занимает каждый импорт, но вы пробовали поток для каталога вместо файла?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...