有人可以解释垃圾收集器的行为吗?

我正在玩C#中的垃圾收集器(或者更确切地说是CLR?)试图更好地理解C#中的内存管理。

我做了一个小样本程序,它将三个较大的文件读入byte[]缓冲区。 我想看看,如果

  • 我实际上需要做任何事情来处理内存效率
  • 在当前迭代结束后将byte[]设置为null时会产生任何影响
  • 最后,如果它有助于通过GC.Collect()强制垃圾收集

免责声明:我使用Windows任务管理器测量内存消耗并将其四舍五入。 我尝试了好几次,但总的来说还是差不多。

这是我的简单示例程序:

 static void Main(string[] args) { Loop(); } private static void Loop() { var list = new List { @"C:\Users\Public\Music\Sample Music\Amanda.wma", // Size: 4.75 MB @"C:\Users\Public\Music\Sample Music\Despertar.wma", // Size: 5.92 MB @"C:\Users\Public\Music\Sample Music\Distance.wma", // Size: 6.31 MB }; Console.WriteLine("before loop"); Console.ReadLine(); foreach (string pathname in list) { // ... code here ... Console.WriteLine("in loop"); Console.ReadLine(); } Console.WriteLine(GC.CollectionCount(1)); Console.WriteLine("end loop"); Console.ReadLine(); } 

对于每个测试,我只更改了foreach循环的内容。 然后我运行程序,在每个Console.ReadLine()我停止并检查Windows任务管理器中进程的内存使用情况。 我记录了已用过的内存,然后继续返回程序(我知道断点;))。 在循环结束后,我将GC.CollectionCount(1)写入控制台,以便查看GC跳转的频率(如果有的话)。

结果


测试1:

 foreach ( ... ) { byte[] buffer = File.ReadAllBytes(pathname); Console.WriteLine ... } 

结果(使用的内存):

 before loop: 9.000 K 1. iteration: 13.000 K 2. iteration: 19.000 K 3. iteration: 25.000 K after loop: 25.000 K GC.CollectionCount(1): 2 

测试2:

 foreach ( ... ) { byte[] buffer = File.ReadAllBytes(pathname); buffer = null; Console.WriteLine ... } 

结果(使用的内存):

 before loop: 9.000 K 1. iteration: 13.000 K 2. iteration: 14.000 K 3. iteration: 15.000 K after loop: 15.000 K GC.CollectionCount(1): 2 

测试3:

 foreach ( ... ) { byte[] buffer = File.ReadAllBytes(pathname); buffer = null; GC.Collect(); Console.WriteLine ... } 

结果(使用的内存):

 before loop: 9.000 K 1. iteration: 8.500 K 2. iteration: 8.600 K 3. iteration: 8.600 K after loop: 8.600 K GC.CollectionCount(1): 3 

我不明白的是:

  • 在测试1中,内存随着每次迭代而增加。 因此我想在循环结束时不释放内存。 但GC仍然说它收集了2次( GC.CollectionCount )。 怎么会这样?
  • 在测试2中,显然有助于将buffer设置为null 。 内存低于测试2.但为什么GC.CollectionCount输出2而不是3? 为什么内存使用量不如测试3中的低?
  • 测试3使用最少的内存。 我会说是这样的,因为1.删除了对内存的引用( buffer设置为null ),因此当通过GC.Collect()调用垃圾收集器时,它可以释放内存。 看起来很清楚。

如果有经验丰富的人可以对上面的一些观点有所了解,那对我来说真的很有帮助。 相当有趣的主题imho。

看看你正在将整个WMA文件读入一个数组,我会说这些数组对象是在大对象堆中分配的。 这是一个单独的堆,它以更多malloc类型的方式进行管理(因为压缩垃圾收集在处理大型对象时效率不高)。

大对象堆中的空间是根据不同的规则收集的,并且它不计入主要生成计数,并且即使存储器,您也不会看到测试1和2之间的集合数量的差异正在被重用(所有正在收集的是Array对象,而不是底层字节)。 在测试3中,每次循环时都会强制收集一个集合 – 大对象堆被包含在其中,因此进程的内存使用量不会增加。

TaskManager不是最好的工具。 使用CLR Profiler或简单的事情,使用WriteLine显示GC.GetTotalMemory()

GC的主要目的是分配和取消分配大量小对象。 如果你想学习它,写一些创造了很多(小)字符串的东西。 确保您知道“代际GC”的含义。

您当前的实验正在执行大对象堆(LOH),其中包含一整套其他规则和关注点。

给你一个我认为可能对你有用的链接。

http://msdn.microsoft.com/en-us/magazine/ee309515.aspx

-Joe Yu

您通过任务管理器查看的内存使用情况是针对该过程的。 请记住,CLR代表您的应用程序管理内存,因此您通常不会直接在进程内存使用中看到GC堆的使用情况。

分配和释放内存并不是免费的,所以很明显CLR会尝试对其进行优化以降低成本。 因此,当从堆中收集对象时,您可能也可能看不到释放到操作系统的内存。