2012-01-10 48 views
0

問題的簡短摘要:我創建的tcp服務器每秒接收一個來自發送大約5kb xml和斷開連接的客戶端的連接,但隨着時間的推移,內存在內存中不斷增加,即使我沒有在任何地方存儲xml。C#字符串導致TCP服務器內存泄漏?

我有一個簡單的TCP服務器,我有一個線程在while循環中調用AcceptTcpClient(),然後用新的TcpClient調用ThreadPool.QueueUserWorkItem(HandleTcpClient,tcpClient)。

在功能

現在,線程池運行(如:HandleTcpClient)我做了以下內容:

int bytesRead = networkStream.Read(byteBuffer, 0, 8000); 
if (bytesRead == 0) return null; 
return encoder.GetString(byteBuffer, 0, bytesRead); 

之後,其調用,確實在隊列上的鎖(),並增加了消息的功能到隊列。表單上的另一個線程每隔50ms檢查一次隊列,調用lock()並彈出字符串。

目前,我剛流行過的字符串,然後執行:

XElement xe = XElement.Parse(msg); 
xe = null; 

當我啓動該程序,私營集存儲爲34MB,在10小時內,內存使用率達到251MB。我在窗體上做了一個菜單項,我可以點擊它來調用GC.Collect(),但是這隻能削減2MB的內存,然後程序開始再次增長。

我不知道爲什麼內存會不斷增長,因爲不斷創建的字符串應該被丟棄,而且我不會將任何東西不斷放在堆上。

我使用了.NET Memory Profiler,它顯示字符串char生存實例不斷增加。 enter image description here

我認爲創建一個新的字節數組可能會導致內存泄漏,所以我創建了一個快速的字節緩衝區類,如下所示。任何想法爲什麼記憶保持增長?

class ByteBuffer { static Dictionary<object, byte[]> buffers = new Dictionary<object,byte[]>(); 

    static public void Allocate() { 
     for (int i = 0; i < 90; i++) 
     { 
      buffers.Add(new object(), new byte[8000]); 
     } 

    } 

    static ASCIIEncoding encoder = new ASCIIEncoding(); 

    static public string GetMessage(NetworkStream ns) 
    { 
     foreach (object o in buffers.Keys) 
     { 
      bool lockTaken =false; 

      Monitor.TryEnter(o,TimeSpan.FromMilliseconds(15), ref lockTaken); 

      if (lockTaken) 
      { 
       try 
       { 
        int bytesRead = ns.Read(buffers[o], 0, 8000); 
        if (bytesRead == 0) return null; 
        return encoder.GetString(buffers[o], 0, bytesRead); 
       } 
       catch 
       { 
        return null; 
       } 
       finally 
       { 
        Monitor.Exit(o); 
       } 
      } 
     } 

     return null; 
    } } 
+0

你的內存分析器不能追蹤引用了哪些字符串?如果你有這樣的截圖,那會有很大的幫助。 – 2012-01-10 18:33:28

+0

服務器是否讀取很多不同形式的XML,或者大多數文檔是否具有相同的元素和屬性名稱?你是否手動彙集你的名單? – 2012-01-10 18:42:17

+1

我想知道是什麼調用GetMessage以及如何管理顯示代碼之外的字符串。你應該回TRY塊嗎? – Bengie 2012-01-10 18:54:52

回答

0

字符串/字符數組本身可能不是你的根本問題,它們只是一個症狀。最有可能的是其中包含字符串或char []字段的其他對象的實例,它們被保存在其中。

所有那些XElementNameTable.Entry對象看起來可疑;在將其設置爲空之前,您確定沒有泄露對您的XElement的引用嗎?你是否將它們添加到父文檔中,並在它們超出範圍之前再次忘記Remove()

+0

邁克爾,謝謝你的迴應。我從隊列中取出()一個字符串,並將字符串傳遞給函數: 嘗試 { XElement xe = XElement.Parse(msg); xe = null; } catch(Exception) { } – 2012-01-10 20:41:50

0

我不知道你的隊列隨着時間的推移如何增長,但是它的「容量」可能會不斷增長?

嘗試調用在同一個菜單項下面的函數,你的垃圾收集發佈:

//Sets the capacity to the actual number of elements in the Queue<T>, if that number is less than 90 percent of current capacity. 
public void TrimExcess() 
+0

關於第二個想法,這可能不會導致** string **內存問題。但是,如果您要保留垃圾收集菜單項,那麼您可能想要添加此項。所以我不會刪除帖子。 – jzacharuk 2012-01-11 00:10:10

+0

謝謝,我會試試這個 – 2012-01-11 15:50:08

0

沒有站出來從你這裏有什麼明顯的原因,所以這裏的猜想幾個不同點:

你有沒有使用任何事件處理程序,或許SocketAsyncEventArgs正在用於你的TCP服務器?如果是這樣,他們是否正確地被detatched?

您是否嘗試過將您的NameTable設爲XElement?每個XmlReader(其中XElement.Parse在調用XElement.Load時會在幕後使用)具有XmlNameTable,無論是明確傳遞給構造函數還是由構造函數創建 - 都取決於使用哪個構造函數override。由於遇到字符串,它必須處理(幾乎所有的前綴,本地名稱,處理指令目標名稱和其他一些字符)存儲在此表中出於性能原因(有點像string.Intern,但它自己的私人實習 - 池和散列算法的隨機元素來打敗基於散列衝突的DoS攻擊)。如果您共享閱讀器之間的名稱表處理同樣類型的文件(在你的情況就意味着更換解析帶有明確建立一個XmlReader隨後到XElement.Load呼叫的電話),那麼它有兩個作用:

  1. 它使得解析速度稍微快一點,只要它確實每次看到很多相同的元素和屬性名稱。

  2. 它改變了內存使用的方式,可以與string.Intern相媲美 - 也就是說,如果最終存儲了大量永遠不會再看到的字符串,但是可以使事情變得更糟如果每次確實擊中相同的字符串,情況會更好。

NameTable不是線程安全的,但你可以有NameTable個游泳池,當你用它做每一個返回到池中,並採取一個從池中下一個讀者,除非游泳池是空的。很大程度上,您正在替換已分配和即將死亡(但可能不是gen-0死亡)的流失,其中n是您的應用程序一次最多同時進行的解析線程。 http://hackcraft.github.com/Ariadne/documentation/html/T_Ariadne_Pool_1.htm是爲創建此類池而創建的類。

TcpClient處理完成後是否處置?我認爲這會比僅僅記住一些記憶更大的打擊(也就是說,我希望你在記憶被擱置太久之前缺乏其他資源而崩潰),但是如果他們正在製作它被引入第2代,同時被引用可能需要很長時間才能完成,然後當它們進入最終隊列時,它們將成爲另一個GC根,直到最終確定。雖然我看不到它受到這麼多傷害,但它仍然值得一看。

與池中的想法有關。是否有字符串,你會更好實習,因爲你反覆看到相同的字符串(並讓他們新創建的副本得到gen-0收集)。或者,你有一些代碼可能不應該積極實習嗎?實習可能是減少內存使用量的一個好方法,並且是大規模增加內存的更好方法!

+0

感謝Jon Hanna的迴應!我在看到你的評論之後創建了XElement或調用解析的註釋,但內存仍在增長。另外,在將消息添加到隊列後,我調用tcpClient.Close()。我注意到我在5秒內得到了幾百個連接,當我查看TcpView(程序)時,有大量的TIME_WAIT連接。 – 2012-01-11 15:49:47

+0

@AriesOntheHorizo​​n這是因爲您沒有正確處理TCP正常關機。例如,當'Read'返回'0'時,這意味着連接的另一端正在關閉 - 您應該立即做到這一點。如果沒有正常關機,TCP連接將在「終止」後約2分鐘內存活。 – Luaan 2014-09-22 07:40:54