Garbage Collection of orphaned objects (tree-nodes) works for the "release-exe" but not in VS-debugger
Situation
According to this accepted answer, if the "GC 'sees' a cyclic reference of 2 or more objects which are not referenced by any other objects or permanent GC handles, those objects will be collected."
I wanted to know if garbage collection works for a super simple tree structure that doesn't even have content, just tree-nodes with parent- & child-references.
Imagine you create a root-node add a child to it and then a child to the child and so on, so not really a tree but more like a list (each node has at most one child and one parent).
If we then remove the root's child and all references to node's within this child's subtree as I understand the answer above, the garbage collector should clean up the subtree.
Problem Description
If you have a look at the Main-method in the test-code below, when running the exe from the Release-directory I get the behavior I would expect memory consumption increases upto ~1GB then goes down to ~27MB (after the 1. GC.collect) up again and then down to ~27MB again (for the 2. GC.collect).
Now when running it in the debugger memory consumption for doing this goes up to ~1GB and for the 1.GC.collect memory consumption stays exactly where it was then goes up to 1.6GB withing the 2nd for-loop takes ages there and then I finally get an OutOfMemoryException within the 2nd for-loop.
Questions
Why do I get this behavior in the debugger?
Shouldn't garbage collection work during debugging as well, am I missing some Info about the debugger?Side notes
- I'm using visual studio 2010 Express edition
- I only call GC.Collect() for the specific testing purposes here to be sure that garbage collection should have taken place.(I don't plan to use it normally)
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();
}
}
}
I only include the following code in case you want to test it for yourself, it's not really useful
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)
{
开发者_C百科 Children.Add(child);
}
public void RemoveChild(TreeNode child)
{
Children.Remove(child);
}
}
}
This is by design. The lifetime of an object reference in a method is extended to the end of the method when the debugger is attached. This is important to make debugging easy. Your TreeNode class keeps both a reference to its parent and its children. So any reference to a tree node keeps the entire tree referenced.
Including the child reference, it keeps the removed section of the tree referenced. While it is no longer in scope by the time you call GC.Collect(), it still exists in the method's stack frame. Scope is a language feature, not a runtime feature. Without a debugger, the jitter tells the garbage collector that the child reference is no longer live at the end of the for loop. So its referenced nodes can be collected.
Note that you won't get OOM when you set child explicitly to null:
for (int i = 0; i < 15000000; i++)
{
TreeNode child = new TreeNode(node);
node = child;
child = null;
}
Do not write that kind of code, you've made a very artificial example.
精彩评论