Jen*_*ens 2 c# tree garbage-collection visual-studio
根据这个已接受的答案,如果" 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.
为什么我在调试器中得到这种行为?
在调试期间不应该进行垃圾收集工作,我是否遗漏了一些关于调试器的信息?
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
我只包含以下代码,以防你想自己测试它,它并没有真正有用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Tree
{
class TreeNode
{
public TreeNode Parent { get; private set; }
public List<TreeNode> 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<TreeNode>();
Parent = parent;
if(parent != null) // the root node doesn't have a parent-node
parent.AddChild(this);
}
public TreeNode(TreeNode parent, List<TreeNode> children)
{
// since we are creating a new node we need to create its List of children
Children = new List<TreeNode>();
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是设计的.在附加调试器时,方法中对象引用的生命周期将扩展到方法的末尾.这对于简化调试非常重要.TreeNode类保留对其父级及其子级的引用.因此,对树节点的任何引用都会保留引用的整个树.
包括子引用,它保留引用的树的已删除部分.虽然在调用GC.Collect()时它不再在范围内,但它仍然存在于方法的堆栈帧中.范围是语言功能,而不是运行时功能.如果没有调试器,抖动会告诉垃圾收集器在for循环结束时子引用不再存在.因此可以收集其引用的节点.
请注意,当您将child显式设置为null 时,您将不会获得OOM :
for (int i = 0; i < 15000000; i++)
{
TreeNode child = new TreeNode(node);
node = child;
child = null;
}
Run Code Online (Sandbox Code Playgroud)
千万不能写那种代码,您已经有了一个非常人工的例子.