【问题标题】:Free memory of byte[]字节[]的空闲内存
【发布时间】:2015-08-17 18:49:07
【问题描述】:

我目前正在努力解决 c# 中的内存使用问题。

我目前正在使用的工具能够上传和下载文件。为此,它使用字节数组作为文件内容的缓冲区。在上传或下载操作之后,我可以处理 WebResponse 和 Stream(+Reader/Writer) 对象,但是字节数组在内存中永远保持活动状态。它超出了范围,我什至将它设为“null”,所以我猜垃圾收集永远不会运行。

在搜索时,我发现很多文章建议永远不要手动运行 GC,但是拥有一个不断占用 100 甚至 1000 MB 内存的简约后台应用程序(使用时间越长它会不断增加)是什么但体面。

那么,在这种情况下,如果不推荐使用 GC,还有什么办法呢?

编辑 3 / 解决方案:我最终使用了一个 16kb 字节的缓冲区,该缓冲区填充了来自文件 i/o 的数据。之后,缓冲区内容被写入 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);
            }
        }
    }

【问题讨论】:

  • 你怎么知道字节数组在内存中还活着?
  • 你必须引用你没有擦除的内存。您不必擦除字节数组的内容,您必须删除对它的所有引用,GC 将重用内存。
  • 一些相关代码可能会有所帮助。可能您正在做的事情使 GC 更难回收内存(例如在某处保留对它的引用)。
  • 能否确认手动运行GC.Collect()是否成功回收内存? (当然只是为了诊断目的)。而且,这些字节数组有多大?这可能是问题的一部分。
  • 显示代码显示证据。如果您有证据表明字节数组在内存中存在,那么这也证明您持有对它的引用。该引用使其保持活力。

标签: c# memory-management


【解决方案1】:

问题的核心是您从文件中读取一大块。如果文件非常大(准确地说大于 85,000 字节),那么您的字节数组将存储在 LOH(大对象堆)中。

如果您阅读了“大型对象堆”(如果您在 google 上搜索到了有关该主题的大量信息),您会发现它被 GC 收集的频率往往低于其他堆区域,更不用说默认情况下它也不会压缩内存,这会导致碎片并最终导致“内存不足”异常。

在您的情况下,您需要做的就是使用固定大小的字节数组缓冲区(例如:4096)以较小的块读取和写入字节,而不是尝试一次读取所有文件。换句话说,您将几个字节读入缓冲区,然后将它们写出。然后你在同一个缓冲区中再读一些,然后再写出来。然后你会一直循环执行,直到你读完整个文件。

请参阅:Here 以获取有关如何以较小的块读取文件的文档,而不是使用

File.ReadAllBytes(localFilePath);

通过这样做,您将始终在任何给定时间处理合理数量的字节,当您完成时,GC 及时收集不会有问题。

【讨论】:

  • 现在我使用的是较小的byte[]s,问题似乎消失了。这是信息最丰富的答案。谢谢!
【解决方案2】:

只需编写更智能的代码。根本不需要将整个文件加载到 byte[] 中以将其上传到 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);
    }
 }

未经测试,应该在球场上。

【讨论】:

    【解决方案3】:

    垃圾回收对于大型对象是不同的 - 并且仅适用于 .NET Framework 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

    【讨论】:

    • 我希望你不是建议他应该启用压缩 LOH 来解决他的问题。这不是一个好主意。
    • 视情况而定。这是一个快速的解决方案,不需要很多代码更改。
    • @SamStanojevic 我认为这只是为了测试它是否仅受 LOH 问题的影响。我会在星期五做一些测试,然后给出反馈。
    猜你喜欢
    • 2023-04-03
    • 1970-01-01
    • 2018-08-21
    • 2012-09-18
    • 2018-06-06
    • 1970-01-01
    • 1970-01-01
    • 2021-02-27
    • 2016-12-24
    相关资源
    最近更新 更多