C#中的垃圾收集未执行。为什么?

C#中的垃圾收集未执行。为什么?,c#,.net,memory-management,garbage-collection,C#,.net,Memory Management,Garbage Collection,我尝试了一个简单的实验来验证垃圾收集器的功能。中有关自动内存管理的参考(MSDN)。对我来说,它听起来像是C++中的一个共享指针。如果对象的引用计数器变为零,则垃圾回收器将释放它 所以我试着在我的主窗体中创建一个函数。该函数是在我的主窗体的所示事件函数中调用的,该函数在构造函数之后执行。这是实验代码 public void experiment() { int[] a = new int[100000]; int[] b = new int[100

我尝试了一个简单的实验来验证垃圾收集器的功能。中有关自动内存管理的参考(MSDN)。对我来说,它听起来像是C++中的一个共享指针。如果对象的引用计数器变为零,则垃圾回收器将释放它

所以我试着在我的主窗体中创建一个函数。该函数是在我的主窗体的所示事件函数中调用的,该函数在构造函数之后执行。这是实验代码

    public void experiment()
    {
        int[] a = new int[100000];
        int[] b = new int[100000];
        int[] c = new int[100000];
        int[] d = new int[100000];

        a = null;
        b = null;
        c = null;
        d = null;
    }
结果如下:

内存分配前

内存分配后

在离开功能范围之前

离开功能范围后


为什么即使在设置为null后,垃圾收集器也不释放阵列a、b、c、d分配的内存?

垃圾收集器是一种高度优化的复杂软件。它经过优化,使您的程序尽可能快地运行,并且这样做不会占用太多内存

因为释放内存的过程需要一些时间,所以垃圾收集器通常会等待运行它,直到程序使用了大量内存。然后它一次完成所有的工作,这会导致程序在相对较长的时间后出现一个小的延迟(而不是之前的许多小的延迟,这会降低程序的速度)

所有这些都意味着垃圾收集器运行的时间是不可预测的

您可以多次调用您的测试(循环中有一些Sleep()),并观察内存使用情况慢慢增加。当您的程序开始消耗大量可用物理内存时,其内存使用率将突然降至接近零

有两个函数(如
GC.Collect()
)强制执行多个级别的垃圾收集,但是强烈建议不要使用它们,除非您知道自己在做什么,因为这会使软件速度变慢,并阻止垃圾收集器以最佳方式工作。

不会在每次内存释放时运行垃圾收集器,因为它会消耗系统资源。因此,根据不断增长的内存大小,定期调用垃圾收集器。它将清除所有未引用的内存泄漏


垃圾收集器也可以使用GC.Collect()方法显式调用,但不建议显式使用。

即使它确实在内部取消了内存分配,也没有义务将其返回到操作系统。它将假定将来会请求更多内存并回收页面。操作系统的编号不知道程序如何选择使用它声明的内存


如果您确实想要明确地声明和释放内存,那么您必须通过Pinvoke不安全代码调用VirtualAlloc()。

垃圾收集的成本很高。您只希望它尽可能少地运行。理想情况下永远不会。因此,系统将尽可能地延迟垃圾收集,基本上直到内存耗尽

分配内存是昂贵的。一旦运行时分配了一些内存,它通常不会再次释放它,即使它当前不需要它,因为如果它在程序运行时的某个时间需要那么多内存,它很可能在将来的某个时间需要类似数量的内存,并且希望避免再次分配内存

因此,即使在测试期间发生了垃圾收集,您也不会在任务管理器或流程资源管理器中看到它,因为任务管理器无论如何都不会释放它


你所描述的被称为a。但是,所有当前存在的CLI VES实现都使用跟踪GC。跟踪GCs不计算引用;它们只在运行时跟踪它们。跟踪GC在实际跟踪对象图之前不会注意对象是否仍然可访问,并且仅在需要运行集合时(即内存不足时)才会跟踪对象图。

您链接到的文章中已包含部分信息。有几个迹象表明您观察到的行为是正确的:

。。。垃圾收集器可以(但不要求)将对象视为不再使用

。。。后来某个时候

GC.Collect() 至少对于旧(非并发)版本的垃圾收集器来说,一件重要的事情是,垃圾收集器运行在不同的线程上。您可以在调试器中验证:

0:003> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
                                      PreEmptive   GC Alloc           Lock
       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
   0    1 1b08 0058f218      a020 Enabled  025553ac:02555fe8 0058b868     1 MTA
   2    2 1e9c 005a78c8      b220 Enabled  00000000:00000000 0058b868     0 MTA (Finalizer)
0:003> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                     60          71cb9000 (   1.778 Gb)           88.91%
<unknown>                                84           986f000 ( 152.434 Mb)  67.09%    7.44%
Image                                   189           2970000 (  41.438 Mb)  18.24%    2.02%
...
终结器线程执行垃圾收集。在操作期间,所有其他线程都将挂起,因此在重组期间,任何线程都不能修改对象

但为什么这很重要

它解释了为什么垃圾收集不会立即应用,无论是在您的场景中,还是在调用
GC.Collect()
执行垃圾收集时。要运行垃圾收集器,还需要一个线程开关。因此,非并发垃圾收集所需的最低代码是

GC.Collect();
Thread.Sleep(0);
如果您关心内存管理,请务必查看

空闲内存 此外,还没有人解释过,使用任务管理器查看内存消耗是不可靠的

.NET直接作用于虚拟内存,即使用虚拟内存管理器。它不使用堆,即堆管理器。相反,它使用自己的内存管理,称为托管堆

.NET从Windows(内核)获取内存。假设它从Windows获得了一块新的内存,其中没有.NET对象。从Windows的角度来看,内存消失了(给了.NET)。然而,从.NET的角度来看,它是免费的,可以被对象使用

同样,您可以在调试器中观察到:

0:003> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
                                      PreEmptive   GC Alloc           Lock
       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
   0    1 1b08 0058f218      a020 Enabled  025553ac:02555fe8 0058b868     1 MTA
   2    2 1e9c 005a78c8      b220 Enabled  00000000:00000000 0058b868     0 MTA (Finalizer)
0:003> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                     60          71cb9000 (   1.778 Gb)           88.91%
<unknown>                                84           986f000 ( 152.434 Mb)  67.09%    7.44%
Image                                   189           2970000 (  41.438 Mb)  18.24%    2.02%
...
所以你可以看到8.5MB从.NET的角度来看是免费的,但是还没有归还给Windows(现在还没有),而且还会继续