很抱歉进入"为什么要收集?"再次。在C#进程中,通常不鼓励手动收集内存以释放RAM。我总体上同意。
要做到这一点,你需要:
GC.Collect()
为什么这是无用的(并且可能是反效率的)被调用了几个原因。我总体上同意,但我不知道这是否有危险。
如果我们不这样做,它会发生(至少对于服务器GC和.NET 4.5),系统可能会为您的进程专用几GB物理内存,而这些物理内存并非真正被活动对象使用。特别是LOH可能永远不会被压缩并且使用大量实际上没有C#内存管理器观点的RAM。我每天都会遇到这样的问题:系统专用的物理内存为20 GB,在几小时前的RAM密集型计算高峰期使用,但只有一小部分仍在使用中。
据我所知,机器上仍然没有明显的性能问题。通常认为这绝不是一个问题,我(几乎)同意它。论证的主要观点是" free"进程无法访问LOH中的RAM,因此系统(Windows)至少在RAM不足时将其移至磁盘。这个答案解释得非常好:When is memory, allocated by .NET process, released back to Windows
从其他流程的角度来看......仍有一些"担心"。
我看到的第一点是,如果另一个进程对RAM有紧急需求,系统需要时间将未使用的内存传输到磁盘。预防性压缩可能会阻止它。
但另一点更令人担忧。系统真的可以将未使用的RAM移动到磁盘吗?如果LOH碎片化,99%的可用空间与1%的已用空间交错,该怎么办?我猜这个系统适用于大段,因此几乎没有任何段实际上是" 0%使用"。也许它更复杂或者可能是错误的。你怎么知道的?
我对那些对此有一定经验的人感兴趣。您是否观察过理论"您不需要收集"出了问题,出于某种原因它已经被证明是健康的吗? (你知道为什么)。
答案 0 :(得分:2)
在现代操作系统中,不分配RAM 1 。您(您的进程)在自己的地址空间内分配。操作系统如何使用物理RAM备份(必要时),这在很大程度上是透明的。
您在 地址空间中所做的事情与其他流程完全无关。
另请参阅:Raymond Chen的挑衅性标题为It's the address space stupid 2
系统真的可以将未使用的RAM移动到磁盘吗?
这不是“已使用”或“未使用”RAM的问题。当操作系统 3 在物理页面上运行不足时,它会使用一些合适的策略识别要驱逐的页面(例如,Least Recently Used是一个简单的解决方案)。然后它必须决定如何处理页面。
如果幸运的话,它挑选的页面要么是文件映像(例如可执行文件的一部分)支持,要么是因为它最后写入磁盘而未被更改。如果是这种情况,它可以在不执行任何额外I / O的情况下逐出页面。如果它运气不好,它会安排I / O,然后继续寻找免费的页面。
它一直在 4 执行此操作 4 。而且它还在做其他事情,例如使用其他未使用的页面作为文件系统缓存。
您遇到真正问题的唯一时间是主动工作的所有页面的总和(每个流程的工作集合的总和)大于RAM页面,通常可用于进程。此时,由于系统不断将页面写入磁盘并检索它们,因此您将发生颠簸。但是在这一点上,GC几乎肯定无济于事,因为我们知道所有这些页面都在积极使用 - 它们都包含可到达的对象。
1 有一些方法可以要求地址空间始终由真正的RAM支持并且永远不会交换,但这不是通常所做的事情,你必须故意不做事来实现这一点
2 我知道这是对政治报价的重复,但这并不意味着这里的所有读者都必须认识到这一点,过去有些人认为我推荐这篇文章是真的我字面上称他们为愚蠢。
3 我试图在这里成为一般人,但我认识到我可能会偏离“任何”操作系统将在Windows中专门做什么。
4 PDC10: Mysteries of Windows Memory Management Revealed: Part Two
答案 1 :(得分:1)
我已经在这个问题上工作了一段时间。结论是:
本文说压缩大对象堆Large Object Heap Compaction: Should You Use it?可能很有用。它说了很多真实的东西,实际上很有用,但是有点肤浅。压缩LOH的有用性必须与系统对虚拟内存的分页一起研究。
总结文章:
默认情况下,大对象堆从不压缩。压缩LOH可以提高您自己的过程的性能,从而更快地分配大型对象。最重要的是,它允许其他进程使用更多的RAM。 但这是有代价的:压缩LOH不可忽略。这就是为什么默认行为是不压缩它的原因。您必须权衡利弊,并找到适当的策略来触发压缩:就像每隔XX分钟一样。
现在,我将解释为什么这是几乎错误的。您需要阅读这篇文章,解释如何管理虚拟内存:Physical and Virtual Memory in Windows 10。
如果LOH内部的某些块是空闲的,则将永远无法访问它们。因此,系统能够将这些块移出RAM。真实对象使用的LOH中的每个活动(已使用)块的大小均大于85k。页面大小为4k(在32位和64位Windows中均是):这些页面中的每一个都可以移出RAM。一个简单的推理可以告诉您,无法从RAM中移出的LOH中的可用空间的比例始终很小。要么您有较大的可用块,然后它们的大多数页面都可以移出RAM,要么您有较小的可用块,然后它们的大小比已用的块小。在这两种情况下,LOH中都没有太多不能移出RAM的可用空间。
我测试了它。我编写了一个创建大小为10 GB的分段LOH的过程,其中只有1%处于活动状态(100 MB),将1 mb的活动块与100 mb的空闲块交织在一起。只有1%的内存被积极地用于计算。在我的16 GB笔记本电脑上,我可以顺利运行其中的三个过程。他们并不慢。当第一个启动时,系统为它提供了10 GB的工作集。当第二个过程开始时,该工作集减少了,而对两个过程的速度没有很大的影响。同样的第三。几分钟后,每个进程的工作集达到了预期的100 MB。他们的虚拟内存只有10 GB。
每个过程的巡航速度都非常理想。但是系统需要将这些GB写入磁盘。这确实减慢了启动过程的速度。这花费了时间和资源。页面文件增长。压缩每个过程的LOH本来是更好的策略。因此,是的,压缩LOH更好,但是系统的默认策略相当好。可能还有一些更细微的问题:当您重新使用已移动到磁盘的空闲块时,究竟会发生什么:系统是否需要从文件中读取它?
还有与性能无关的实际问题。当您在一家在数百台计算机上运行数百个进程的公司中工作时,您想要监视和检测潜在的RAM溢出。对于零散的堆,测量工作集的大小会产生误导。 .NET不能轻松访问“可达对象的总大小”。因此,当您看到一个“使用” 10 GB(工作集大小)的进程时,您不知道这10 GB是否确实需要。压缩LOH有助于使指标“工作集大小”切实可用。
注意:这是用于我的实验的代码:
public static class ExampleFragmentation
{
public static void Main()
{
var mega = 1024 * 1024;
var smallArrays = new int[100][];
var largeArrays = new int[100][];
for (int i = 0; i < 100; i++)
{
smallArrays[i] = CreateArray(mega);
largeArrays[i] = CreateArray(100 * mega);
}
largeArrays = null;
while (true)
{
DateTime start = DateTime.Now;
long sum = 0;
for (int i = 0; i < 100; i++)
foreach (var array in smallArrays)
foreach (var element in array)
sum += element;
double time = (DateTime.Now - start).TotalSeconds;
Console.WriteLine("sum=" + sum + " " + time + " s");
}
}
private static int[] CreateArray(int bytes)
{
var array = new int[bytes / sizeof(int)];
for (int i = 0; i < array.Length; i++)
array[i] = 1;
return array;
}
}