我制作了一个64位WPF测试应用程序。随着我的应用程序运行并打开任务管理器,我会查看系统内存使用情况。我看到我使用2GB,而且我有6GB可用。
在我的应用程序中,单击“添加”按钮将新的1GB字节数组添加到列表中。我看到我的系统内存使用量增加了1GB。我点击添加总共6次,填充我开始时可用的6GB内存。
我单击“删除”按钮6次以从列表中删除每个数组。删除的字节数组不应该被我控制中的任何其他对象引用。
当我删除时,我看不到记忆力下降。 但这对我来说没关系,因为我知道GC是非确定性的,所有这一切。 我认为GC会根据需要收集。
所以现在内存看起来很满,但是期待GC在需要时收集,我再次添加。 我的电脑开始滑入和跳出磁盘晃动昏迷。 为什么GC不收集? 如果那不是时候做,那么什么时候?
作为一个完整性检查,我有一个强制GC的按钮。当我推动它时,我很快就恢复了6GB。这不能证明我的6个数组没有被引用,并且如果GC知道/想要收集COULD吗?
我已经阅读了很多说我不应该调用GC.Collect()但是如果GC在这种情况下不收集,我还能做什么呢?
private ObservableCollection<byte[]> memoryChunks = new ObservableCollection<byte[]>();
public ObservableCollection<byte[]> MemoryChunks
{
get { return this.memoryChunks; }
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
// Create a 1 gig chunk of memory and add it to the collection.
// It should not be garbage collected as long as it's in the collection.
try
{
byte[] chunk = new byte[1024*1024*1024];
// Looks like I need to populate memory otherwise it doesn't show up in task manager
for (int i = 0; i < chunk.Length; i++)
{
chunk[i] = 100;
}
this.memoryChunks.Add(chunk);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Could not create another chunk: {0}{1}", Environment.NewLine, ex.ToString()));
}
}
private void RemoveButton_Click(object sender, RoutedEventArgs e)
{
// By removing the chunk from the collection,
// I except no object has a reference to it,
// so it should be garbage collectable.
if (memoryChunks.Count > 0)
{
memoryChunks.RemoveAt(0);
}
}
private void GCButton_Click(object sender, RoutedEventArgs e)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
答案 0 :(得分:18)
作为一个完整性检查,我有一个强制GC的按钮。当我推动它时,我很快就恢复了6GB。这不能证明我的6个数组没有被引用,并且如果GC知道/想要收集COULD吗?
你最好不要问When does the GC automatically collect "garbage" memory?
。在我的头顶:
OutOfMemoryException
时,会触发一个完整的GC来首先尝试并回收可用内存。如果收集后没有足够的连续内存可用,则会抛出OOM异常。启动垃圾收集时,GC确定需要收集哪些代(0,0 + 1或全部)。每一代都有一个由GC确定的大小(它可以随着应用程序的运行而改变)。如果只有第0代将超过其预算,那么这是唯一将收集垃圾的一代。如果在第0代中存活的对象将导致第1代超过其预算,那么第1代也将被收集,其幸存的对象将被提升为第2代(这是Microsoft实现中的最高代)。如果超过第2代的预算,将收集垃圾,但不能将对象提升到更高的一代,因为不存在。
所以,这里有重要的信息,在GC启动的大多数 common 方式中,只有在第0代和第1代都满时才会收集第2代。此外,您需要知道超过85,000个字节的对象不会存储在具有第0代,第1代和第2代的普通GC堆中。它实际上存储在所谓的大对象堆(LOH)中。 LOH中的内存仅在FULL集合期间释放(即,在收集第2代时);永远不会仅代收集0或1。
为什么GC没有收集?如果那不是时候做,那么什么时候?
现在应该很清楚为什么GC从未自动发生过。您仅在LOH上创建对象(请记住,int
类型,您使用它们的方式,是在堆栈上分配的,不必收集) 。你永远不会填满第0代,所以GC永远不会发生。 1
你也是在64位模式下运行它,这意味着你不太可能遇到我上面列出的另一种情况,当整个应用程序中的内存不足时会发生集合分配某个对象。 64位应用程序的虚拟地址空间限制为8TB,因此在您遇到此情况之前需要一段时间。在此之前,你很可能会耗尽物理内存和页面文件空间。
由于GC尚未发生,因此Windows开始从页面文件中的可用空间为您的应用程序分配内存。
我已经阅读了很多说我不应该调用GC.Collect()但是如果GC在这种情况下不收集,我还能做什么呢?
如果您需要编写此类代码,请致电GC.Collect()
。更好的是,不要在测试之外编写这种代码。
总之,我还没有公正地对待CLR中的自动垃圾收集主题。我建议通过msdn博客文章阅读它(实际上非常有趣),或者已经提到过,Jeffery Richter的优秀书籍CLR Via C#,第21章。
1 我假设你明白GC的.NET实现是世代垃圾收集器。简单来说,这意味着新创建的对象位于编号较低的一代,即第0代。当运行垃圾收集时,发现某一代中的对象具有GC根(不是“垃圾”),它将被提升到下一代。这是一项性能改进,因为GC可能需要很长时间并且会损害性能。我们的想法是,较高代的物体通常具有较长的寿命,并且在应用中会更长时间,所以它不需要像下一代那样检查那一代的垃圾。您可以在this wikipedia article中阅读更多内容。您会注意到它也被称为短暂的 GC。
2 如果您不相信我,在删除其中一个块后,有一个函数可以创建一大堆随机字符串或对象(我建议不要使用基本数组)在你达到一定数量的空间后,你会看到一个完整的GC,释放你在LOH中分配的记忆。
答案 1 :(得分:5)
这是在LOH(大对象堆)上进行的。只有在执行第2代集合时才会清除它们。正如汉斯在评论中所说,如果这是真正的代码,你将需要更多的内存。
对于咯咯笑声,您可以致电GC.GetGeneration(chunk)
,看看它会返回2
。
请参阅CLR via C#,Jeffrey Richter第3版(第588页)。
答案 2 :(得分:2)
对于需要分配大量数据而不是发布的真实代码,考虑在完成大对象时手动调用GC(GC.Collect best practice question)。
您还可以通过在较小(小于80K)的块中进行分配,将对象从LOH转移到普通堆。