垃圾收集孤立对象(树节点)适用于“release-exe”,但不适用于VS-debugger

情况

根据这个已接受的答案 ,如果“ GC”看到’2个或更多对象的循环引用,这些对象未被任何其他对象或永久GC句柄引用,则将收集这些对象。

我想知道垃圾收集是否适用于一个甚至没有内容的超级简单树结构,只是带有父级和子级引用的树节点。

想象一下,你创建一个根节点为它添加一个子节点,然后为子节点添加一个子节点等等,所以不是一个树,而是更像一个列表(每个节点最多只有一个子节点和一个父节点)。

如果我们理解上面的答案,然后我们删除了root的子节点以及该子节点内节点的所有引用,垃圾收集器应该清理子树。

问题描述

如果你看一下下面测试代码中的Main方法,当从Release-directory运行exe时,我得到的行为我预计内存消耗会增加到~1GB然后下降到~27MB(在1之后)。 GC.collect再次向上然后再降低到~27MB(对于2. GC.collect)。

现在当它在调试器中运行时,内存消耗达到~1GB,而1.GC.collect内存消耗保持精确到达1.6GB,第二个for循环需要时间,然后我最后在第二个for循环中得到一个OutOfMemoryException

问题

为什么我在调试器中会出现这种行为?
在调试期间不应该进行垃圾收集工作,我是否遗漏了一些关于调试器的信息?

旁注

  • 我正在使用visual studio 2010 Express版
  • 我只在这里调用GC.Collect()用于特定的测试目的,以确保应该进行垃圾收集。(我不打算正常使用它)

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Tree { class Program { static void Main(string[] args) { TreeNode root = new TreeNode(null); // the null-argument is the parent-node TreeNode node = root; for (int i = 0; i < 15000000; i++) { TreeNode child = new TreeNode(node); node = child; } root.RemoveChild(root.Children[0] ); node = root; GC.Collect(); for (int i = 0; i < 15000000; i++) { TreeNode child = new TreeNode(node); node = child; } root.RemoveChild(root.Children[0]); node = root; GC.Collect(); Console.ReadLine(); } } } 

我只包括以下代码,以防你想自己测试它,它并没有真正有用


 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Tree { class TreeNode { public TreeNode Parent { get; private set; } public List Children { get; set; } public TreeNode(TreeNode parent) { // since we are creating a new node we need to create its List of children Children = new List(); Parent = parent; if(parent != null) // the root node doesn't have a parent-node parent.AddChild(this); } public TreeNode(TreeNode parent, List children) { // since we are creating a new node we need to create its List of children Children = new List(); Parent = parent; if (parent != null) // the root node doesn't have a parent-node parent.AddChild(this); Children = children; } public void AddChild(TreeNode child) { Children.Add(child); } public void RemoveChild(TreeNode child) { Children.Remove(child); } } } 

这是设计的。 在附加调试器时,方法中对象引用的生命周期将扩展到方法的末尾。 这对于简化调试非常重要。 您的TreeNode类保留对其父级及其子级的引用。 因此,对树节点的任何引用都会保留引用的整个树。

包括引用,它保留引用的树的已删除部分。 虽然它在调用GC.Collect()时不再处于范围内,但它仍然存在于方法的堆栈帧中。 范围是语言function,而不是运行时function。 如果没有调试器,抖动会告诉垃圾收集器在for循环结束时引用不再存在。 因此可以收集其引用的节点。

请注意,当您将child显式设置为null时,您将不会获得OOM:

  for (int i = 0; i < 15000000; i++) { TreeNode child = new TreeNode(node); node = child; child = null; } 

不要写那种代码,你已经做了一个非常人为的例子。