【问题标题】:How to free up all used RAM in a C# process?如何在 C# 进程中释放所有使用的 RAM?
【发布时间】:2022-02-03 10:00:16
【问题描述】:

关于垃圾收集器的另一个问题。关于 .net 环境中垃圾收集器的行为有很多问题,但很少有明确的答案。我的问题很简单,我希望如果有人能清楚地回答它应该可以帮助很多会经过这里的人。

所以,这是一个简单的代码(.net core 6.0.101)

var arraySize = int.MaxValue / 4;
var rnd = new Random();
var arrays = new List<byte[]>();

// Initial memory footprint
Console.WriteLine($"[0] Memory: {GC.GetTotalMemory(false) / 1000000:N0} MB");
for (int i = 1; i < 5; i++)
{
    var array = new byte[arraySize];
    // fill the array to be sure that .net doesn't do obscure memory optimizations
    rnd.NextBytes(array);
    arrays.Add(array);
}

Console.WriteLine($"[Before GC] Memory: {GC.GetTotalMemory(true) / 1000000:N0} MB");

arrays.Clear();
arrays = null;

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
GC.WaitForPendingFinalizers();
Console.WriteLine($"[After GC] Memory: {GC.GetTotalMemory(true) / 1000000:N0} MB");

Console.WriteLine("Press a key to exit");
Console.ReadLine();

现在是结果。

C:\testsgc>dotnet run
[0] Memory: 0 MB
[Before GC] Memory: 2 147 MB
[After GC] Memory: 536 MB
Press a key to exit

进程内存占用(任务管理器):

  • 初始内存:5.6 MB
  • 填充变量后:2 056,5 MB
  • 垃圾收集后:520,1 MB

在启动时,该进程占用 5,6 MB 或 RAM。一个字节数组需要大约 500 MB 的内存。该列表被清除并分配为 null(不需要)。

那么问题来了,如何释放最后一个字节数组?

只有精确可验证的答案会被标记为已解决。

【问题讨论】:

  • 尝试在WaitForPendingFinalizers 之后运行另一个GC.Collect。此外,请确保您没有在附加调试器的情况下运行,并且您是为 Release 而不是为 Debug 构建的。变量的作用域生命周期被人为地延长,以支持在变量最后一次使用后命中断点并且仍然能够检查它,例如,循环中的 array 变量可能会一直持续到方法结束。
  • 感谢@LasseV.Karlsen 的帮助!不幸的是,我可以确认程序在没有附加任何调试器的情况下运行,并且我还尝试在WaitForPendingFinalizers 之后运行GC.Collect,但没有任何成功。我什至尝试了一个 for 循环或 100 次 GC.Collect 迭代。
  • 我无法确切告诉您为什么会这样,但是当您运行最终报告时,您会将这 5 个数组中的 1 个保留在内存中。添加额外的 GC.Collect 没有帮助,但在 Release 构建中运行会。这很可能是我在此处发挥的其他评论中提到的人为延长寿命。但是,即使在循环内将array 设置为null 也不会更改调试版本的这一点。因此,我无法解释为什么数组仍在内存中。但是,在 var array = new 之后添加其中一个 Console.WriteLine 调用会显示完全相同的内存使用情况。
  • 试试dotnet run --configuration Release
  • 配置发布完成了这项工作!

标签: c# memory garbage-collection


【解决方案1】:

当您从调试版本运行程序时,您的一个子数组仍保留在内存中。

让我们稍微调整一下你的程序,在你的循环中添加一个Console.WriteLine,如下所示:

for (int i = 1; i < 5; i++)
{
    var array = new byte[arraySize];
    Console.WriteLine($"[In loop] Memory: {GC.GetTotalMemory(true) / 1000000:N0} MB");

你的程序的输出现在变成了:

> dotnet run
[0] Memory: 0 MB
[In loop] Memory: 536 MB
[In loop] Memory: 1 073 MB
[In loop] Memory: 1 610 MB
[In loop] Memory: 2 147 MB
[Before GC] Memory: 2 147 MB
[After GC] Memory: 536 MB

如您所见,循环中的第一个报告大小匹配。所以很可能其中一个数组在垃圾收集中幸存下来。

“解决此问题”的典型方法是在调用 GC.WaitForPendingFinalizers 之后添加另一个调用 GC.Collect,但这在这里没有区别。

不再使用的变量实际上可以进行垃圾回收,但前提是您不使用调试器运行并且已启用优化(也称为 Release-build)进行编译。

因此,如果我们尝试仅从 Release-build 运行程序:

> dotnet run --configuration Release
[0] Memory: 0 MB
[Before GC] Memory: 2 147 MB
[After GC] Memory: 0 MB

虽然我无法解释为什么其中一个子阵列仍然活着并且在踢,但它似乎是相关的。在将array 添加到arrays 列表之后,我尝试在循环内将array 设置为null,但这并没有改变任何东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-04
    • 2020-10-07
    • 2014-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-16
    相关资源
    最近更新 更多