2010-01-04 48 views
6

编辑2:我只想确保我的问题是明确的:为什么在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; 
} 
+3

什么是parseMessages? – Fredou 2010-01-04 02:25:37

+0

在那里,添加它。 – 2010-01-04 03:10:50

+0

如果您最终使用StreamReader,则不需要'FileStream'。检查构造函数。 – 2010-01-05 05:27:46

回答

5

正如许多人所提到的,这可能只是GC的人工产物,并不像您期待的那样快速地清理内存。对于C#,Java等托管语言而言,这是正常的。如果您对该用法感兴趣,那么您确实需要知道分配给程序的内存是否是免费的。与此相关的问题是:

  1. 程序运行有多长时间?它是一个连续运行的服务类型程序吗?
  2. 在执行期间是否继续从操作系统分配内存或者是否达到稳定状态? (你运行了足够长的时间以找出?)

你的代码看起来不像是“内存泄漏”。在托管语言中,您确实不会像在C/C++中那样获得内存泄漏(除非您使用不安全的或外部库是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块的其他部分中隐藏错误或者将来会发生更改。

+0

看起来你是对的,没有内存泄漏。并感谢您对我的代码的评论,我仍然在抓C#。 – 2010-01-05 02:10:33

1

有一件事你可能想尝试,暂时迫使每次运行后GC.Collect的该GC是非常聪明的,不会回收内存,直到是感觉收集的费用是值得任何恢复的内存的价值。编辑:我只是想补充说,重要的是要明白,手动调用GC.Collect是一个不好的做法(对于任何正常使用情况。异常==也许是一个游戏的负载函数或somesuch)。你应该让垃圾收集器决定什么是最好的,因为它通常会有更多的信息,而不仅仅是关于系统资源和其他基于收集行为的信息。

+2

不要忘记把它删除后,不要保留在那里收集,坏主意 – Fredou 2010-01-04 02:27:39

+0

哈哈,我只是写在,谢谢:) – Gregory 2010-01-04 02:29:45

0

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

编辑

在看的过程中似乎做什么我有一个建议,如果不是太晚了,而不是解析HTML文件。

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

+0

请你详细说明最后一点,请吗?我不想创建另一个HTML文件,我的应用程序的全部目的是创建一个粗体HTML日志的精简版本:P – 2010-01-04 03:25:06

0

try-catch块可以使用finally(清理)。如果你看看使用语句做了什么,它相当于最终尝试catch。是的,运行GC也是一个好主意。如果没有编译此代码,给它一个尝试是很难肯定的说...

此外,处置这家伙用正确使用:

的FileStream destf =新的FileStream(destFileName,FileMode.Append);

查找有效的C#第2版

2

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

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

如果您在parseMessages()中解析大型文件,您可以通过将StringBuilder本身或StreamReader传递到parseMessages()来更高效地完成此操作。这将使得只能将文件的一部分随时加载到内存中,而不是使用当前将整个日志文件强制到内存中的ToString()。

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

看起来你已经配置好资源,但是GC可能是奋力分配,然后下一次迭代开始前解除分配在时间的大内存块,所以你看到的增加内存使用情况。

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

看起来你的parseMessages()和HtmlAgilityPack(一个非常有用的库,顺便说一下)的使用看起来很可能有一些大的,可能分配的内存正在为每个逻辑执行。

HtmlAgility为内部各种节点分配内存,当与缓冲区数组和主函数中的分配结合使用时,我更加确信GC正在承受很大的压力。要停止猜测并获得一些真实的指标,我将运行ProcessExplorer并添加列以显示GC Gen 0,1,2集合列。然后运行您的应用程序并观察收集的数量。如果您在这些列中看到大量数字,那么GC正在努力工作,您应该重新设计以使用更少的内存分配。

另外,来自Microsoft的免费CLR Profiler 2.0提供了在您的应用程序内的.NET内存分配的很好的可视化表示。

+0

“但是实际上,您将日志文件中的所有行读入fb,然后转换为一个字符串“。 是的,因为然后parseMessages()使用HtmlAgilityPack来取消文件。 – 2010-01-04 03:28:49

+0

@Daniel,HtmlAgilityPack也可以从StreamReader等读取(将它传递给Load()方法)。使用Stream可以避免将整个字符串/文件加载到内存中。 – Ash 2010-01-04 13:50:36

0

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

一个很好的工具来指出这种事情是SciTech的.NET Memory Profiler。他们有一个免费的两周试用期。

简而言之,您可以尝试注释掉某些库函数,并查看问题是否消失,如果您只是读取文件而对数据无所作为。

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

4

没有内存泄漏。如果您使用Windows任务管理器来测量您的.NET应用程序使用的内存,则您无法清楚地了解正在进行的操作,因为GC以一种复杂的方式管理内存,而任务管理器无法反映该内存。

一位MS工程师写了一篇很棒的文章article,说明为什么似乎内存泄露的.NET应用程序可能不是,并且它有深入解释GC实际工作原理的链接。每个.NET程序员都应该阅读它们。

+0

我会将此标记为已接受,但我无法选择2个答案。谢谢! – 2010-01-05 02:11:14

0

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

+0

我从来没有听说过与HtmlDocument严重的内存泄漏问题。你能引用一个参考文献还是提供一个例子? – 2012-05-24 15:28:06