byte[] 的空闲内存
Free memory of byte[]
我目前正在努力解决 C# 中的内存使用问题。
我目前正在使用的工具能够上传和下载文件。为此,它使用字节数组作为文件内容的缓冲区。在上载或下载操作之后,我可以处理 WebResponse 和 Stream(+Reader/Writer) 对象,但是字节数组永远保留在内存中。它超出了范围,我什至 'null' 它,所以我猜垃圾收集永远不会 运行ning。
在搜索时,我发现很多文章建议永远不要手动 运行 GC,但是有一个简约的后台应用程序不断占用 100 甚至 1000 MB 的 RAM(随着时间的推移不断增加)你用它)一点都不体面。
那么,如果不推荐使用 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);
}
}
}
问题的核心是您从文件中读取一大块。如果文件非常大(准确地说大于 85,000 字节),那么您的字节数组将存储在 LOH(大对象堆)中。
如果您阅读了 'large object heap'(如果您 google 它,则有很多关于该主题的信息),您会发现它被收集的频率往往较低GC 比其他堆区域更重要,更不用说默认情况下它也不会压缩内存,这会导致碎片并最终导致 'out of memory' 异常。
在您的情况下,您需要做的就是使用固定大小的字节数组缓冲区(例如:4096)以较小的块读取和写入字节,而不是尝试一次读取所有文件。换句话说,您将几个字节读入缓冲区,然后将它们写出。然后你再读一些到同一个缓冲区,然后再写出来。然后你一直在循环中这样做,直到你读完整个文件。
请参阅:Here 获取有关如何分块读取文件而不是使用
的文档
File.ReadAllBytes(localFilePath);
通过这样做,您将始终在任何给定时间处理合理数量的字节,GC 将在您完成后及时收集没有问题。
只需编写更智能的代码。根本不需要将整个文件加载到 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);
}
}
未经测试,应该在球场上。
大型对象的垃圾回收有所不同 - 并且仅适用于 .NET Framework 4.5.1 及更新版本。
此代码将释放大对象堆:
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
我目前正在努力解决 C# 中的内存使用问题。
我目前正在使用的工具能够上传和下载文件。为此,它使用字节数组作为文件内容的缓冲区。在上载或下载操作之后,我可以处理 WebResponse 和 Stream(+Reader/Writer) 对象,但是字节数组永远保留在内存中。它超出了范围,我什至 'null' 它,所以我猜垃圾收集永远不会 运行ning。
在搜索时,我发现很多文章建议永远不要手动 运行 GC,但是有一个简约的后台应用程序不断占用 100 甚至 1000 MB 的 RAM(随着时间的推移不断增加)你用它)一点都不体面。
那么,如果不推荐使用 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);
}
}
}
问题的核心是您从文件中读取一大块。如果文件非常大(准确地说大于 85,000 字节),那么您的字节数组将存储在 LOH(大对象堆)中。
如果您阅读了 'large object heap'(如果您 google 它,则有很多关于该主题的信息),您会发现它被收集的频率往往较低GC 比其他堆区域更重要,更不用说默认情况下它也不会压缩内存,这会导致碎片并最终导致 'out of memory' 异常。
在您的情况下,您需要做的就是使用固定大小的字节数组缓冲区(例如:4096)以较小的块读取和写入字节,而不是尝试一次读取所有文件。换句话说,您将几个字节读入缓冲区,然后将它们写出。然后你再读一些到同一个缓冲区,然后再写出来。然后你一直在循环中这样做,直到你读完整个文件。
请参阅:Here 获取有关如何分块读取文件而不是使用
的文档File.ReadAllBytes(localFilePath);
通过这样做,您将始终在任何给定时间处理合理数量的字节,GC 将在您完成后及时收集没有问题。
只需编写更智能的代码。根本不需要将整个文件加载到 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);
}
}
未经测试,应该在球场上。
大型对象的垃圾回收有所不同 - 并且仅适用于 .NET Framework 4.5.1 及更新版本。
此代码将释放大对象堆:
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();