这个函数中的内存泄漏在哪里?

时间:2010-01-04 02:22:15

标签: c# .net memory-leaks io

Edit2:我只是想确定我的问题是清楚的:为什么,在AppendToLog()的每次迭代中,应用程序使用15mb以上? (原始日志文件的大小)

我有一个名为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);                

}

在每次迭代中,RAM使用量增加15mb左右。这是功能:(看起来很长但很简单)

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;
}

8 个答案:

答案 0 :(得分:6)

正如许多人所提到的,这可能只是GC的一个工件,没有像你期望的那样快地清理内存存储。对于托管语言,例如C#,Java等,这是正常的。如果您对该用法感兴趣,您确实需要查明分配给您的程序的内存是否可用。与此相关的问题是:

  1. 您的程序运行多长时间?它是一个连续运行的服务类型程序吗?
  2. 在执行范围内是否继续从操作系统分配内存或是否达到稳定状态? (你运行它的时间足够长吗?)
  3. 您的代码看起来不会有“内存泄漏”。在托管语言中,您实际上不会像在C / C ++中那样出现内存泄漏(除非您使用 unsafe 或外部库是C / C ++)。但是,您确实需要注意保留或隐藏的引用(如已被告知要删除项目但未将内部数组的元素设置为null的Collection类)。通常,除非将对象的引用存储到对象/类变量中,否则对堆栈引用的对象(本地和参数)不能“泄漏”。

    对您的代码的一些评论:

    1. 您可以通过将StringBuilder预分配到至少适当的大小来减少内存的分配/取消分配。因为你知道你需要将整个文件保存在内存中,所以将它分配给文件大小(这实际上会给你一个比你需要的大一点的缓冲区,因为你没有存储换行字符序列,但文件可能是有他们):

      FileInfo fi = new FileInfo(path);
      StringBuilder fb = new StringBuilder((int) fi.Length);
      

      您可能希望在获取文件长度之前确保该文件存在,并使用fi进行检查。请注意,我只是将长度缩减为int而不进行错误检查,因为根据您的问题文本,您的文件小于2GB。如果不是这种情况,那么你应该在投射之前验证长度,如果文件太大,可能会抛出异常。

    2. 我建议您删除代码中的所有variable = null语句。这些不是必需的,因为它们是堆栈分配的变量。同样,在这种情况下,它不会帮助GC,因为该方法将不会存在很长时间。因此,通过让它们在代码中创建额外的混乱并且更难理解。

    3. ParseMessages方法中,您捕获NullReferenceException并假设它只是一个非文本节点。这可能会导致将来出现混乱的问题。由于数据中可能存在的某些内容通常会发生,因此您应检查代码中的条件,例如:

      if (node.Text != null)
          sb.Append(node.Text.Trim()); //Name
      

      例外是代码中的异常/意外情况。为NullReferenceException分配重要含义而不是有空引用可以(可能会)隐藏同一try块的其他部分中的错误,或者将来隐藏更改。

答案 1 :(得分:4)

没有内存泄漏。如果您使用Windows任务管理器来测量.NET应用程序使用的内存,则无法清楚地了解正在发生的事情,因为GC以任务管理器未反映的复杂方式管理内存。

一位MS工程师写了一篇很棒的article,关于为什么似乎泄漏内存的.NET应用程序可能没有,并且它有关于GC实际工作方式的深入解释的链接。每个.NET程序员都应该阅读它们。

答案 2 :(得分:2)

我会仔细查看为什么需要将字符串传递给parseMessages,即fb.ToString()。

您的代码注释表明这会返回每行内容的数组。但实际上,您实际上是将日志文件中的所有行读入fb,然后转换为字符串。

如果要解析parseMessages()中的大文件,可以通过将StringBuilder本身或StreamReader传递给parseMessages()来更有效地执行此操作。这样就可以在任何时候只将文件的一部分加载到内存中,而不是使用当前强制整个日志文件进入内存的ToString()。

由于垃圾回收,您不太可能在.NET应用程序中发生真正的内存泄漏。您不希望使用任何大型资源(如文件),因此您实际内存泄漏的可能性更小。

看起来您已经处理好了资源,但GC可能很难在下一次迭代开始之前及时分配并释放大内存块,因此您会看到内存使用量的增加。

虽然GC.Collect()可能允许您强制释放内存,但我强烈建议您在尝试通过GC手动管理内存之前查看上述建议。

[更新]看到你的parseMessages()和HtmlAgilityPack的使用(顺便说一下,这是一个非常有用的库),看起来可能会为每个logile执行一些大的,可能是大量的内存分配。

HtmlAgility在内部为各种节点分配内存,当与缓冲区数组和主函数中的分配结合使用时,我更加确信GC会承受很大的压力以便跟上。

要停止猜测并获得一些真实的指标,我会运行ProcessExplorer并添加列以显示GC Gen 0,1,2集合列。然后运行您的应用程序并观察集合的数量。如果您在这些列中看到大量数字,那么GC正在苦苦挣扎,您应该重新设计使用更少的内存分配。

或者,Microsoft的免费CLR Profiler 2.0在您的应用程序中提供了.NET内存分配的可视化表示。

答案 3 :(得分:1)

您可能想尝试的一件事是在每次运行后暂时强制GC.Collect。 GC是非常智能的,并且在感觉收集的费用值得任何恢复的内存值之前不会回收内存。

编辑:我只是想补充一点,了解手动调用GC.Collect是一个不好的做法(对于任何正常的用例。异常==或许是游戏的加载函数或某些东西)。您应该让垃圾收集器最好地决定什么,因为它通常会提供更多信息,而不是系统资源等基于其收集行为的信息。

答案 4 :(得分:1)

try-catch块可以使用finally(清理)。如果你看一下using语句的作用,它等同于try catch finally。是的,运行GC也是一个好主意。如果没有编译这些代码并试一试,很难肯定地说......

另外,使用以下方法妥善处理此人:

FileStream destf = new FileStream(destFileName,FileMode.Append);

查找有效的C#第二版

答案 5 :(得分:0)

在将它们设置为null之前,我会手动清除消息数组和stringbuilder。

修改

看看这个过程似乎做了什么我得到了一个建议,如果不是太晚而不是解析一个html文件。

创建数据集模式,并使用它来编写和读取xml日志文件,并使用xsl文件将其转换为html文件。

答案 6 :(得分:0)

我没有看到任何明显的内存泄漏;我的第一个猜测是它在图书馆里。

SciTech的.NET Memory Profiler是一个很好的工具来解决这类问题。他们有一个为期两周的免费试用。

除此之外,您可以尝试注释掉一些库函数,如果您只是读取文件并且不对数据执行任何操作,请查看问题是否消失。

另外,你在哪里寻找内存使用统计数据?请记住,任务管理器报告的统计数据并不总是非常有用或反映实际的内存使用情况。

答案 7 :(得分:0)

HtmlDocument类(据我可以确定)从托管代码中使用时会出现严重的内存泄漏。我建议使用XMLDOM解析器(虽然这需要格式良好的文档,但那是另一个+)。