3 Stimmen

Garbage Collection von verwaisten Objekten (Baumknoten) funktioniert in der "release-exe" aber nicht im VS-Debugger

Situation

Nach diesem akzeptierte Antwort , wenn die " GC 'sieht' eine zyklische Referenz von 2 oder mehr Objekten, die nicht von anderen Objekten oder permanenten GC-Handles referenziert werden, werden diese Objekte gesammelt."

Ich wollte wissen, ob Garbage Collection für eine super einfache Baumstruktur funktioniert, die nicht einmal Inhalt hat, nur Baumknoten mit Eltern- und Kind-Referenzen.

Stellen Sie sich vor, Sie erstellen einen Wurzelknoten, fügen diesem ein Kind hinzu und dann ein Kind dem Kind und so weiter, also nicht wirklich ein Baum, sondern eher eine Liste (jeder Knoten hat höchstens ein Kind und ein Elternteil).

Wenn wir dann das Kind der Wurzel und alle Verweise auf Knoten innerhalb des Teilbaums dieses Kindes entfernen, wie ich die obige Antwort verstehe, sollte der Garbage Collector den Teilbaum bereinigen.

Beschreibung des Problems

Wenn Sie einen Blick auf die Main-Methode im unten stehenden Testcode werfen, wird bei Wenn ich die Exe aus dem Release-Verzeichnis ausführe, erhalte ich das Verhalten, das ich erwarten würde Der Speicherverbrauch steigt auf ~1GB, geht dann auf ~27MB (nach dem 1. GC.collect) wieder hoch und dann wieder auf ~27MB (für das 2. GC.collect).

Wenn nun Ausführung im Debugger Der Speicherverbrauch für diesen Vorgang steigt auf ~1GB und für die 1.GC.collect bleibt der Speicherverbrauch genau dort, wo er war, steigt dann auf 1.6GB innerhalb der 2. for-Schleife und dauert dort ewig und dann bekomme ich endlich eine OutOfMemoryException innerhalb der 2. for-Schleife.

Fragen

Warum tritt dieses Verhalten im Debugger auf?
Sollte die Garbage Collection nicht auch während des Debuggens funktionieren, fehlt mir da eine Info über den Debugger?

Nebenbemerkungen

  • Ich verwende Visual Studio 2010 Express Edition
  • Ich rufe GC.Collect() nur zu Testzwecken auf, um sicher zu sein, dass die Garbage Collection stattgefunden hat (ich habe nicht vor, sie normalerweise zu verwenden).

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();
    }
  }
}

Ich füge den folgenden Code nur für den Fall ein, dass Sie ihn selbst testen möchten, er ist nicht wirklich nützlich


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);
    }

  }
}

6voto

Hans Passant Punkte 894572

Dies ist beabsichtigt. Die Lebensdauer einer Objektreferenz in einer Methode wird bis zum Ende der Methode verlängert, wenn der Debugger angehängt wird. Dies ist wichtig, um die Fehlersuche zu erleichtern. Ihre TreeNode-Klasse behält sowohl einen Verweis auf seine Eltern als auch auf seine Kinder. Jeder Verweis auf einen Baumknoten hält also den gesamten Baum referenziert.

Einschließlich der Kind referenziert wird, bleibt der entfernte Abschnitt des Baums referenziert. Obwohl er zum Zeitpunkt des Aufrufs von GC.Collect() nicht mehr im Gültigkeitsbereich ist, befindet er sich immer noch im Stackframe der Methode. Scope ist ein Sprachmerkmal, kein Laufzeitmerkmal. Ohne einen Debugger teilt der Jitter dem Garbage Collector mit, dass die Kind Referenz am Ende der for-Schleife nicht mehr aktiv ist. Die referenzierten Knoten können also gesammelt werden.

Beachten Sie, dass Sie kein OOM erhalten, wenn Sie die Kind explizit auf Null setzen:

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

Do no Wenn Sie diese Art von Code schreiben, haben Sie ein sehr künstliches Beispiel geschaffen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X