Где утечка памяти в этой функции?

Edit2: Я просто хочу удостовериться, что мой вопрос ясен: Да ведь на каждом повторении AppendToLog (), приложение использует на 15 МБ больше? (размер исходного файла журнала)

У меня есть функция под названием AppendToLog (), который получает путь к файлу документа HTML, делает некоторый парсинг и добавляет его в файл. Это называют этим путем:

this.user_email = uemail;
string wanted_user = wemail;

string[] logPaths;
logPaths = this.getLogPaths(wanted_user);

foreach (string path in logPaths)
{              

    this.AppendToLog(path);                

}

На каждом повторении Использование оперативной памяти увеличивается на 15 МБ или около этого. Это - функция: (взгляды долго, но это просто),

public void AppendToLog(string path)
{

Encoding enc = Encoding.GetEncoding("ISO-8859-2");
StringBuilder fb = new StringBuilder();
FileStream sourcef;
string[] messages;

try
{
    sourcef = new FileStream(path, FileMode.Open);
}
catch (IOException)
{
    throw new IOException("The chat log is in use by another process."); ;
}
using (StreamReader sreader = new StreamReader(sourcef, enc))
{

    string file_buffer;
    while ((file_buffer = sreader.ReadLine()) != null)
    {
        fb.Append(file_buffer);
    }                
}

//Array of each line's content
messages = parseMessages(fb.ToString());

fb = null;

string destFileName = String.Format("{0}_log.txt",System.IO.Path.GetFileNameWithoutExtension(path));
FileStream destf = new FileStream(destFileName, FileMode.Append);
using (StreamWriter swriter = new StreamWriter(destf, enc))
{
    foreach (string message in messages)
    {
        if (message != null)
        {
            swriter.WriteLine(message);
        }
    }
}

messages = null;

sourcef.Dispose();
destf.Dispose();


sourcef = null;
destf = null;
}

Я был днями с этим, и я не знаю, что сделать :(

Править: Это - ParseMessages, функция, которая использует HtmlAgilityPack для лишения частей журнала HTML.

public string[] parseMessages(string what)
{
StringBuilder sb = new StringBuilder();
HtmlDocument doc = new HtmlDocument();

doc.LoadHtml(what);            

HtmlNodeCollection messageGroups = doc.DocumentNode.SelectNodes("//body/div[@class='mplsession']");
int messageCount = doc.DocumentNode.SelectNodes("//tbody/tr").Count;

doc = null;

string[] buffer = new string[messageCount];

int i = 0;

foreach (HtmlNode sessiongroup in messageGroups)
{
    HtmlNode tablegroup = sessiongroup.SelectSingleNode("table/tbody");

    string sessiontime = sessiongroup.Attributes["id"].Value;

    HtmlNodeCollection messages = tablegroup.SelectNodes("tr");
    if (messages != null)
    {
        foreach (HtmlNode htmlNode in messages)
        {
            sb.Append(
                    ParseMessageDate(
                        sessiontime,
                        htmlNode.ChildNodes[0].ChildNodes[0].InnerText
                    )
                ); //Date
            sb.Append(" ");

            try
            {
                foreach (HtmlTextNode node in htmlNode.ChildNodes[0].SelectNodes("text()"))
                {
                    sb.Append(node.Text.Trim()); //Name
                }
            }
            catch (NullReferenceException)
            {
                /*
                 * We ignore this exception, it just means there's extra text
                 * and that means that it's not a normal message
                 * but a system message instead
                 * (i.e. "John logged off")
                 * Therefore we add the "::" mark for future organizing
                 */
                sb.Append("::");
            }
            sb.Append(" ");

            string message = htmlNode.ChildNodes[1].InnerHtml;
            message = message.Replace(""", "'");
            message = message.Replace(" ", " ");
            message = RemoveMedia(message);
            sb.Append(message); //Message
            buffer[i] = sb.ToString();
            sb = new StringBuilder();
            i++;
        }
    }
}
messageGroups = null;
what = null;
return buffer;
}
7
задан Daniel Sorichetti 4 January 2010 в 03:31
поделиться

8 ответов

[

] Как многие уже упоминали, это, вероятно, всего лишь артефакт того, что ГХ не очищает память так быстро, как вы этого ожидаете. Это нормально для управляемых языков, таких как C#, Java и др. Вам действительно нужно выяснить, свободна ли выделенная вашей программе память или нет, если вы заинтересованы в таком использовании. Вопросы, связанные с этим, следующие:[

]. [
    ] [
  1. ] Как долго работает ваша программа? Это программа служебного типа, которая работает непрерывно? [
  2. ] [
  3. ] На протяжении всего периода выполнения продолжает ли она выделять память из операционной системы или достигает стационарного состояния? (Вы запустили ее достаточно долго, чтобы это выяснить?) [
  4. ] [
] [

] Ваш код не похож на "утечку памяти". В управляемых языках действительно не происходит утечки памяти, как в Си/Си++ (если только вы не используете []опасные[] или внешние библиотеки, которые являются Си/Си++). Однако, случается так, что необходимо следить за ссылками, которые остались или скрыты (например, класс Collection, которому было сказано удалить элемент, но при этом элемент внутреннего массива не был установлен в []null[]). Обычно объекты со ссылками в стеке (локали и параметры) не могут "утечь", если только вы не храните ссылку объекта(ов) в переменных объекта/класса.[

]. [

] Некоторые комментарии к вашему коду: [

] [
    ] [
  1. ][

    ]Можно уменьшить выделение/делегирование памяти, предварительно выделив строковый конструктор []StringBuilder[], по крайней мере, до нужного размера. Так как вы знаете, что вам понадобится хранить весь файл в памяти, выделите его по размеру файла (это даст вам буфер, который чуть больше требуемого, так как вы не храните символьные последовательности новой строки, а файл, скорее всего, имеет их):[

    ]. [
    ][]FileInfo fi = новый FileInfo(path);
    StringBuilder fb = new StringBuilder((int) fi.Length);
    [][
    ] [

    ]Вы можете убедиться в существовании файла до получения его длины, используя []fi[] для проверки. Обратите внимание, что я просто уменьшаю длину до []int[] без проверки на ошибки, так как ваши файлы имеют размер менее 2 ГБ, исходя из вашего текста вопроса. Если это не так, то вы должны проверить длину перед кастингом, возможно, выбрасывая исключение, если файл слишком большой.[

    ][
  2. ]. [
  3. ][

    ] Я бы рекомендовал удалить все утверждения [] переменной = null[] в Вашем коде. В этом нет необходимости, так как это переменные выделения стека. Также, в данном контексте, это не поможет ГХ, так как метод не будет жить долгое время. Таким образом, имея их, вы создаете дополнительный беспорядок в коде, и это сложнее понять.[

    ][
  4. ] [
  5. ][

    ] В своем методе []ParseMessages[] вы ловите []NullReferenceException[] и предполагаете, что это просто нетекстовый узел. Это может привести к путанице в будущем. Так как это то, что обычно ожидается [] в результате того, что может существовать в данных[], то следует проверить условие в коде, например:[

    ]. [
    ] [] если (node.Text != null)
        sb.Append(node.Text.Trim()); //Name
    [][
    ] [

    ] Исключения делаются для исключительных/неожиданных условий в коде. Присвоение значимого значения []NullReferenceException[] более чем то, что была нулевая ссылка, может (скорее всего, будет) скрыть ошибки в других частях того же самого блока []try[] сейчас или с будущими изменениями.[

    ][
  6. ]. [
]
6
ответ дан 6 December 2019 в 15:23
поделиться

Утечки памяти нет. Если вы используете Windows Task Manager для измерения памяти, используемой вашим .NET приложением, вы не получаете четкого представления о том, что происходит, потому что GC управляет памятью сложным способом, который Task Manager не отражает.

Инженер MS написал отличную статью о том, почему .NET приложения, которые кажутся протекающими, скорее всего, не являются протекающими, и в ней есть ссылки на очень подробное объяснение того, как GC на самом деле работает. Каждый .NET программист должен прочитать их.

.
4
ответ дан 6 December 2019 в 15:23
поделиться

Я бы внимательно посмотрел, зачем нужно передавать строку для разбора сообщений, т.е. fb.ToString().

В вашем комментарии к коду сказано, что это возвращает массив содержимого каждой строки. Однако на самом деле вы читаете все строки из лог-файла в fb, а затем преобразуете их в строку.

Если вы разбираете большие файлы в parseMessages(), то вы можете сделать это гораздо эффективнее, передав в parseMessages() сам StringBuilder или StreamReader. Это позволило бы в любой момент времени загружать в память только часть файла, в отличие от использования ToString(), которая на данный момент заставляет весь лог-файл работать в памяти.

Утечка памяти в .NET-приложении менее вероятна благодаря сборке мусора. Не похоже, что вы используете большие ресурсы, такие как файлы, поэтому вероятность реальной утечки памяти еще меньше.

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

Хотя GC.Collect() может позволить принудительное выделение памяти, я бы настоятельно рекомендовал рассмотреть вышеприведенные предложения, прежде чем прибегать к ручному управлению памятью с помощью GC.

.

[Обновление] Видя ваш parseMessages() и использование HtmlAgilityPack (кстати, очень полезная библиотека), похоже, есть некоторые большие и, возможно, многочисленные выделения памяти, выполняемые для каждого logile.

HtmlAgility выделяет память для различных узлов внутренне, в сочетании с вашим буферным массивом и распределениями в основной функции я еще больше уверен, что на ГХ оказывается большое давление, чтобы не отставать.

Чтобы перестать гадать и получить какие-то реальные метрики, я бы запустил ProcessExplorer и добавил колонки, чтобы показать колонки коллекций ГХ Gen 0,1,2. Затем запустил ваше приложение и наблюдал за количеством коллекций. Если вы видите в этих столбцах большое количество коллекций, то ГХ испытывает трудности, и вы должны перепроектировать его таким образом, чтобы использовать меньшее количество выделенной памяти.

Кроме того, бесплатный CLR Profiler 2.0 от Microsoft обеспечивает хорошее визуальное представление .NET-распределений памяти в вашем приложении.

.
2
ответ дан 6 December 2019 в 15:23
поделиться

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

Edit: Я просто хотел добавить, что важно понимать, что вызов GC.Collect вручную - плохая практика (для любого нормального случая использования. Ненормальный == возможно, функция загрузки для игры или подобное). Вы должны позволить сборщику мусора решить, что лучше, так как он, как правило, будет иметь больше информации, чем доступно для вас о системных ресурсах и т.п., на которых будет основываться его поведение при сборе.

.
1
ответ дан 6 December 2019 в 15:23
поделиться

Блок пробной ловли может использовать окончательную (очистку). Если вы посмотрите на то, что делает оператор use, это эквивалентно попытке catch finally. Да, запуск GC также является хорошей идеей. Не компилируя этот код и не давая ему попробовать, трудно сказать наверняка ...

Также, распорядитесь этим парнем правильно, используя:

FileStream destf = новый FileStream(destFileName, FileMode.Append);

Look up Effective C# 2-е издание

.
1
ответ дан 6 December 2019 в 15:23
поделиться

Я бы вручную очистил массив сообщений и строкостроитель перед установкой их в нуль.

edit

глядя на то, что процесс, кажется, делает, я получил предложение, если еще не поздно вместо того, чтобы разбирать html-файл.

создать схему набора данных и использовать ее для записи и чтения xml лог-файла и использовать xsl-файл для преобразования его в html файл.

0
ответ дан 6 December 2019 в 15:23
поделиться

Я не вижу явных утечек памяти; мое первое предположение - это что-то в библиотеке.

Хорошим инструментом для выяснения такого рода вещей является .NET-профилировщик памяти от SciTech. У них есть бесплатная двухнедельная пробная версия.

Вскоре вы можете попробовать прокомментировать некоторые функции библиотеки и посмотреть, устранится ли проблема, если вы просто прочитаете файлы и ничего не будете делать с данными.

Также, где вы ищете статистику использования памяти? Имейте в виду, что статистика, сообщаемая диспетчером задач, не всегда очень полезна или отражает реальное использование памяти.

0
ответ дан 6 December 2019 в 15:23
поделиться

Класс HtmlDocument (насколько я могу определить) имеет серьезную утечку памяти при использовании из управляемого кода. Я рекомендую вместо этого использовать синтаксический анализатор XMLDOM (хотя для этого требуются хорошо сформированные документы, но это еще один плюс).

0
ответ дан 6 December 2019 в 15:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: