我在 C# 中遇到了奇怪的内存泄漏,这对我来说没有任何意义。这是重现此问题的简单示例。 我有一个简单的类,可以保存字节数组:
public class Record
{
public byte[] Bytes { get; set; }
}
我还创建了一个方法,可以显示进程占用的总内存(以兆字节为单位),以及托管堆内存的大小:
static void ShowMemory()
{
var managedTotal = Math.Round(GC.GetTotalMemory(true) / (1024.0 * 1024.0), 2);
var proc = System.Diagnostics.Process.GetCurrentProcess();
var memoryTotal = Math.Round(proc.PrivateMemorySize64 / (1024.0 * 1024.0), 2);
proc.Dispose();
Console.WriteLine($"Total Memory: {memoryTotal} Mb, Managed Memory: {managedTotal} Mb");
}
现在这是有趣的部分,这里是演示问题的代码片段:
var list = new List<Record>();
// here I just add some records to the list and allocate Bytes array for each of the records.
for (int i = 0; i < 10000; i++)
{
list.Add(new Record() { Bytes = new byte[100000] });
}
// Now here I display the memory. The result I get is:
// Total Memory: 1071.84 Mb, Managed Memory: 954.56 Mb
ShowMemory();
// here I loop through the list again and reallocate the Bytes array
foreach (var record in list)
{
record.Bytes = new byte[100000];
}
// Now here I display the memory again. The result I get is:
// Total Memory: 1994.46 Mb, Managed Memory: 954.68 Mb
ShowMemory();
现在您可以看到显示的结果是:
Total Memory: 1071.84 Mb, Managed Memory: 954.56 Mb
Total Memory: 1994.46 Mb, Managed Memory: 954.68 Mb
我不明白在我再次循环列表并重新分配字节后,总内存的大小增长了一倍,而堆内存几乎保持不变,这怎么可能。此外,如果我将此代码放在单独的方法中,完成后,垃圾收集器仅重新调整托管内存,而总内存仍然很大。
有人可以解释一下这里发生了什么吗?也许如何避免它?由于这个问题,我运行的另一个进程耗尽了内存。
仅供参考,我正在使用
.Net 8
运行于 Ubuntu 22
这不是您问题的答案,而是更多建议您应该使用 .NET 8.0 中提供的池内存实用程序。例如:
for (int i = 0; i < 10000; i++)
{
list.Add(new Record() { Bytes = ArrayPool<byte>.Shared.Rent(100000) });
}
ShowMemory();
foreach (var record in list)
{
ArrayPool<byte>.Shared.Return(record.Bytes);
record.Bytes = ArrayPool<byte>.Shared.Rent(100000);
}
在我的机器上打印:
Total Memory: 1274.11 Mb, Managed Memory: 1250.87 Mb
Total Memory: 1274.38 Mb, Managed Memory: 1250.87 Mb
当然,您需要做更多的工作,因为
Rent()
可能会返回一个更大的块。我建议研究一下 Memory、Span、MemoryOwner 等。