2010-01-04 47 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