C#发送文件时的巨大内存使用情况,System.OutOfMemoryException

时间:2012-11-25 19:09:10

标签: c# exception memory tcp stream

我正在使用TCP通过网络发送/接收文件的程序。 程序发送多个文件,因此在用户退出程序之前,流不会关闭。

我面临的问题是,当我发送700mb文件时,我的服务器程序私有内存增长到700,000 K并严重削弱了我的计算机性能。当尝试发送另一个700mb文件时,服务器会抛出System.OutOfMemoryException

有人可以告诉我我做错了什么,或者没做错?

服务器端代码:

   using (   FileStream fs = new FileStream("dracula.avi", FileMode.Open, FileAccess.Read))                            
  {                                                        
   byte[] data = new byte[fs.Length];
   int remaining = data.Length;
   int offset = 0;

   strWriter.WriteLine("Content-Length: " + data.Length);
   strWriter.Flush();

   Thread.Sleep(1000);
   while (remaining > 0)
        {
            Thread.Sleep(10);
            int read = fs.Read(data, offset, remaining);
            remaining -= read;
            offset += read;

        }

   fs.Flush();
   fs.Close();
 }   

  strm.Write(data, 0, data.Length);
  strm.Flush();
  GC.Collect();

4 个答案:

答案 0 :(得分:4)

您当前正在将整个文件读入内存,即使您只想将其复制到另一个流中。不要那样做。只需一次迭代一个块:读取一个块,写一个块,读一个块,写一个块等等。如果你使用的是.NET 4,你可以使用Stream.CopyTo来实现这个目的。

答案 1 :(得分:3)

你正在缓冲你的阅读,但不是你的写作。该程序正在完成您所告知的操作 - 在发送单个字节之前分配一个巨大的块或内存并填充所有内容。

更好的方法是从文件中读取一小块(为了参数,4096字节),然后将块写入输出流。通过这样做,每个连接只能使用4096个字节,这样可以扩展得更多。

答案 2 :(得分:1)

最好在阅读后立即发送数据块。我没有测试代码,但它应该类似于;

   var bufferLenght = 1024;
   byte[] buffer;

   while (remaining > 0)
   {
            buffer = new byte[1024];
            int len = fs.Read(buffer, offset, bufferLenght);
            remaining -= len;
            offset += len;    
            strm.Write(buffer, 0, len);
   }

答案 3 :(得分:1)

当您用完系统内存时,或者在32位进程中,您的地址空间不足(2000MB)时,通常会出现OOM情况。

你说它可以成功复制一个而不是两个吗?这两个是同时还是连续?你的线程模型是什么?此外,该示例是一个片段,您似乎有一个StreamWriter和一个用于写入的Stream,这些对象是否会消失?

小心GC.Collect。 Microsoft不建议显式调用,因为如果您不正确使用它,它可能导致对象保持活动的时间超过需要。这是因为当您执行GC.Collect时,您正在将对象提升为更高代。根据我的经验,最好确保释放对象并让框架决定GC.Collect的时间/时间。

我会熟悉WinDBG + SOS,这允许你查看堆上的对象。

试试这个:

  1. 启动WinDBG并附加到您的流程
  2. 如果使用4.0,请键入“.loadby sos clr”,否则键入“.loadby sos mscorwks”
  3. 按F5继续
  4. 复制一个文件,等待其完成
  5. 按CTRL + BREAK
  6. 输入“!dumpheap -stat”,查看结果,查找应该消失的对象
  7. For-Each应该消失的对象,抓住MT值
  8. 键入“!dumpheap -mt {0}”将{0}替换为上面步骤
  9. 中的值
  10. 这是一个实例列表,抓取一个对象地址
  11. 键入“!gcroot {0}”将{0}替换为对象地址
  12. 这应该告诉你什么是对象的根源,然后你需要找出如何取消根,例如,空对象不需要。