2015-06-03 89 views
1

我目前在c#中的內存使用量正在掙扎。字節的空閒內存[]

我目前正在使用的工具能夠上傳和下載文件。爲此,它使用字節數組作爲文件內容的緩衝區。在上傳或下載操作後,我可以放棄WebResponse和Stream(+ Reader/Writer)對象,但字節數組永遠保持在內存中。它超出了範圍,我甚至'空'它,所以我猜垃圾收集從未運行。

在搜索過程中,我發現了很多文章,建議永遠不要手動運行GC,但有一個簡約的後臺應用程序,不斷佔用100或甚至1000 MB的RAM(使用時間越長,什麼都不過分。

那麼,如果不推薦使用GC,那麼在這種情況下還能做什麼呢?

編輯3 /解決方案:我結束了使用從文件I/O填充數據的16kb字節緩衝區。之後,將緩衝區內容寫入RequestStream,並採取進一步的操作(更新進度條等)。

編輯2:這似乎與LOH有關。我會在星期五進行測試,然後在這裏記錄結果。

編輯:這是代碼,也許我錯過了一個參考?

internal void ThreadRun(object sender, DoWorkEventArgs e) 
    { 
     BackgroundWorker worker = sender as BackgroundWorker; 

     UploadItem current = Upload.GetCurrent(); 

     if (current != null) 
     { 
      string localFilePath = current.src; 
      string fileName = Path.GetFileName(localFilePath); 
      elapsed = 0; 
      progress = 0; 

      try 
      { 
       string keyString = Util.GetRandomString(8); 

       worker.ReportProgress(0, new UploadState(0, 0, 0)); 

       FtpWebRequest req0 = Util.CreateFtpsRequest("ftp://" + m.textBox1.Text + "/" + keyString, m.textBox2.Text, m.textBox3.Text, WebRequestMethods.Ftp.MakeDirectory); 

       req0.GetResponse(); 

       FtpWebRequest req1 = Util.CreateFtpsRequest("ftp://" + m.textBox1.Text + "/" + keyString + "/" + fileName, m.textBox2.Text, m.textBox3.Text, WebRequestMethods.Ftp.UploadFile); 

       worker.ReportProgress(0, new UploadState(1, 0, 0)); 

       byte[] contents = File.ReadAllBytes(localFilePath); 

       worker.ReportProgress(0, new UploadState(2, 0, 0)); 

       req1.ContentLength = contents.Length; 

       Stream reqStream = req1.GetRequestStream(); 

       Stopwatch timer = new Stopwatch(); 
       timer.Start(); 

       if (contents.Length > 100000) 
       { 
        int hundredth = contents.Length/100; 

        for (int i = 0; i < 100; i++) 
        { 
         worker.ReportProgress(i, new UploadState(3, i * hundredth, timer.ElapsedMilliseconds)); 
         reqStream.Write(contents, i * hundredth, i < 99 ? hundredth : contents.Length - (99 * hundredth)); 
        } 
       } 
       else 
       { 
        reqStream.Write(contents, 0, contents.Length); 
        worker.ReportProgress(99, new UploadState(3, contents.Length, timer.ElapsedMilliseconds)); 
       } 

       int contSize = contents.Length; 
       contents = null; 

       reqStream.Close(); 

       FtpWebResponse resp = (FtpWebResponse)req1.GetResponse(); 

       reqStream.Dispose(); 

       if (resp.StatusCode == FtpStatusCode.ClosingData) 
       { 
        FtpWebRequest req2 = Util.CreateFtpsRequest("ftp://" + m.textBox1.Text + "/storedfiles.sfl", m.textBox2.Text, m.textBox3.Text, WebRequestMethods.Ftp.AppendFile); 

        DateTime now = DateTime.Now; 

        byte[] data = Encoding.Unicode.GetBytes(keyString + "/" + fileName + "/" + Util.BytesToText(contSize) + "/" + now.Day + "-" + now.Month + "-" + now.Year + " " + now.Hour + ":" + (now.Minute < 10 ? "0" : "") + now.Minute + "\n"); 

        req2.ContentLength = data.Length; 

        Stream stream2 = req2.GetRequestStream(); 

        stream2.Write(data, 0, data.Length); 
        stream2.Close(); 

        data = null; 

        req2.GetResponse().Dispose(); 
        stream2.Dispose(); 

        worker.ReportProgress(100, new UploadState(4, 0, 0)); 
        e.Result = new UploadResult("Upload successful!", "A link to your file has been copied to the clipboard.", 5000, ("http://" + m.textBox1.Text + "/u/" + m.textBox2.Text + "/" + keyString + "/" + fileName).Replace(" ", "%20")); 
       } 
       else 
       { 
        e.Result = new UploadResult("Error", "An unknown error occurred: " + resp.StatusCode, 5000, ""); 
       } 
      } 
      catch (Exception ex) 
      { 
       e.Result = new UploadResult("Connection failed", "Cannot connect. Maybe your credentials are wrong, your account has been suspended or the server is offline.", 5000, ""); 
       Console.WriteLine(ex.StackTrace); 
      } 
     } 
    } 
+4

你怎麼知道的字節數組還活着的記憶是什麼? – BrokenGlass

+0

你必須有一個你沒有擦除的內存的引用。您不必擦除字節數組的內容,必須刪除對它的所有引用,並且GC將重新使用該內存。 –

+0

一些相關的代碼可能會有所幫助。有可能你正在做一些讓GC更難回收內存的問題(比如在某處保留引用)。 –

回答

4

核心問題是,您從一個大塊中讀取文件。如果文件非常大(精確地說大於85,000字節),那麼你的字節數組將被存儲在LOH(大對象堆)中。

如果您閱讀了「大型對象堆」(如果您使用google的話,有大量關於該主題的信息),您會發現GC往往比其他堆收集的GC要少得多地區,更不用說它不會默認壓縮內存,這會導致碎片並最終導致「內存不足」異常。在你的情況下,你所要做的就是用較小的塊讀取和寫入字節,使用固定大小的字節數組緩衝區(例如:4096),而不是試圖一次讀取全部文件。換句話說,你在你的緩衝區中讀了幾個字節,然後把它們寫出來。然後你再讀入一些相同的緩衝區,然後再寫出來。你一直在循環,直到你讀完整個文件。

請參閱:https://msdn.microsoft.com/en-us/library/system.io.filestream.read(v=vs.110).aspx瞭解如何以較小的塊讀取文件,而不是使用「File.ReadAllBytes(localFilePath);」。

通過這樣做,您將始終在任何給定時間處理合理數量的字節,以便GC在完成後不會及時收集問題。

+0

Now我使用的是較小的'byte []',問題似乎沒有了。這是最有意義的答案。謝謝! – domi1819

5

只需編寫更智能的代碼。根本不需要將整個文件加載到一個字節[]中以將其上傳到FTP服務器。所有你需要的是一個FileStream。使用它的CopyTo()方法從FileStream複製到從GetRequestStream()獲得的NetworkStream。

如果你想顯示進度,那麼你將不得不復制自己,一個4096字節的緩衝區完成工作。粗略地:

using (var fs = File.OpenRead(localFilePath)) { 
    byte[] buffer = new byte[4096]; 
    int total = 0; 
    worker.ReportProgress(0); 
    for(;;) { 
     int read = fs.Read(buffer, 0, buffer.Length); 
     if (read == 0) break; 
      reqStream.Write(buffer, 0, read); 
      total += read; 
      worker.ReportProgress(total * 100/fs.Length); 
     } 
    } 
} 

未經測試,應該在球場。

2

垃圾收集是大對象不同 - 與.NET框架4.5.1和更新纔可用。

該代碼將釋放大對象堆:

GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; 
GC.Collect(); 

參見: https://msdn.microsoft.com/en-us/library/system.runtime.gcsettings.largeobjectheapcompactionmode(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2

+2

我希望你不是建議他應該讓LOH解決他的問題。這不是一個好主意。 – sstan

+1

這取決於。這是一個快速解決方案,不需要很多代碼更改。 –

+0

@SamStanojevic我認爲這只是爲了測試它是否只受到LOH問題的影響。我會在週五做一些測試,然後給予反饋。 – domi1819

相關問題