进程内存/垃圾收集器的工作

时间:2015-07-26 20:30:47

标签: c# .net memory-management garbage-collection visual-studio-2015

我一直在努力了解Process Memory的工作。所以我尝试了一段代码

    public void OpenFormWithoutList()
    {
        Form2 form = null;
        int index = 0;
        while (index < 5000)
        {
            form = new Form2();
            form.ShowDialog();
            index++;
        }
    }
    public void OpenFormWithList()
    {
        Form2 form = null;
        List<Form> list = new List<Form>();
        int index = 0;
        while (index < 5000)
        {
            form = new Form2();
            list.Add(form);
            form.ShowDialog();
            index++;
        }
        list = null;
    }

在Form2.cs中,我正在关闭OnLoad事件中的表单,因此控件应该再次返回到父表单(Form1)。

当我从start开始分别运行这两个方法时,以下是方法执行后的观察结果:

开始:20 MB OpenFormWithList():29MB

开始:20MB OpenFormWithoutList():25MB

当调用OpenFormWithoutList()时,GC正在收集表单,因此内存使用量不会达到29MB。但是一旦这些方法结束,那么内存使用量也不会回到起始阶段,即20MB。

那么为什么内存不会被清除以及内存的消耗是什么?

2 个答案:

答案 0 :(得分:2)

请记住,垃圾收集在任何实例处理后都不会立即释放内存。它已经过优化,只有在存在内存压力时才会触发和释放内存。因此,如果要测试内存泄漏,则应在执行计数器读数之前手动执行垃圾收集。

GC.Collect();
GC.WaitForPendingFinalizers();

答案 1 :(得分:1)

.NET使用分代垃圾收集器,并且没有像确定性内存分配那样的东西(当然,除非你在任何地方使用不安全的代码和结构)。

这里最相关的部分是,在每次分配时,运行时都会检查自上次尝试垃圾回收以来已分配了多少内存 - 如果这超过了某个阈值,则开始收集。因此它将遍历整个内存(对于第2代集合 - 下一代只收集通过较低代的堆),记下所有没有引用它们的对象,并清除它们。最后,它将压缩堆 - 移动所有对象以在堆中创建一个连续的空间。这是非常重要的,因为.NET并没有在堆中间分配[1] - 它类似于加强堆栈,允许&#34;弹出&#34;从中间。

完成此操作后,所有幸存的对象都将被提升到下一代堆(除非它们已经处于最大生成阶段,在撰写本文时为2)。

这是变体与列表之间的差异,而变体没有。通过更多分配,旧表单实例可以按照您的预期进行回收 - 但只有在首先有足够的分配时才会回收。还有其他隐藏的成本 - 很可能,第一次初始化需要加载一些库或一些共享的初始化。这就是为什么你总是想在做任何测试之前使用某种形式的热身。此外,进程内存无论如何都不是那么重要 - 如果你想解决内存问题,CLR Profiler或类似的东西会更有用。

您可以通过调用GC.Collect强制垃圾收集器执行其工作,但这是不明智的。你几乎不应该真的需要它。只是习惯于对内存分配和释放没有完美的控制 - 你就是在一个多线程,先发制人的多任务,内存虚拟化系统上,现在很可能是分布式的。无论如何,精确控制记忆是一种错觉:D

另一个重点是对编译器和运行时的另一个问题的误解。将null分配给当地人并没有真正做任何事情 - 如果您在调试人员之外运行,本地将有资格收集,只要它不再是用过的。如果您在调试器内部运行,则会为整个范围保留所有本地(当然,以帮助调试)。此外,如果您没有合理的价值来初始化本地人,请避免初始化本地人 - 您在编写显示意外的代码路径时会帮助您自己编译帮助。

[1]请注意,这仅适用于主堆。大对象堆确实允许在中间分配,并且它不紧凑。从.NET 4.5开始,可以选择在LOH上手动强制堆集合。