.net - net - memory leak c++



Zirkuläre Verweise verursachen Speicherleck? (4)

Ich versuche, ein Speicherleck in einer Windows Forms-Anwendung herunterzufahren. Ich schaue jetzt auf ein Formular, das mehrere eingebettete Formulare enthält. Was mich beunruhigt, ist, dass das Kind in seinem Konstruktor einen Verweis auf das Elternformular erstellt und es in einem privaten Mitgliederfeld hält. So scheint es mir, dass Müllabfuhrzeit kommen:

Parent hat einen Verweis auf das untergeordnete Formular über die Steuerelementauflistung (untergeordnetes Formular ist dort eingebettet). Kinderform ist nicht GC'd.

Untergeordnetes Formular hat einen Verweis auf das übergeordnete Formular über das private Mitgliedsfeld. Elternformular ist nicht GC'd.

Ist das ein genaues Verständnis davon, wie der Müllsammler die Situation bewerten wird? Irgendeine Möglichkeit, es zu Testzwecken zu "beweisen"?


Der GC kann mit Zirkelverweisen richtig umgehen, und wenn diese Verweise die einzigen Dinge wären, die die Form am Leben erhalten, dann würden sie gesammelt werden.
Ich hatte viele Probleme damit, dass .net keine Speicher von Formularen zurückgewinnt. In 1.1 gab es einige Bugs um menueitem (glaube ich), was bedeutete, dass sie nicht entsorgt wurden und Speicher verloren gehen konnten. In diesem Fall wurde das Problem durch Hinzufügen eines expliziten Aufrufs zum Ablegen und Löschen der Elementvariablen in der Dispose-Methode des Formulars sortiert. Wir fanden heraus, dass dies auch dazu beitrug, Speicher für einige der anderen Kontrolltypen wiederzugewinnen.
Ich verbrachte auch lange Zeit mit CLR-Profiler, um zu untersuchen, warum Formulare nicht gesammelt wurden. Soweit ich das beurteilen konnte, wurden Referenzen im Rahmen gehalten. Ein pro Formulartyp. Wenn Sie also 100 Instanzen von Form1 erstellen, schließen Sie alle, nur 99 würden ordnungsgemäß wiederhergestellt. Ich habe keinen Weg gefunden, das zu heilen.
Unsere Anwendung ist inzwischen auf .net 2 umgezogen und das scheint viel besser zu sein. Unser Anwendungsspeicher nimmt immer noch zu, wenn wir das erste Formular öffnen und geht nicht zurück, wenn es geschlossen wird, aber ich glaube, dass dies auf JIT-Code und zusätzliche Kontrollbibliotheken zurückzuführen ist, die geladen werden.
Ich habe auch festgestellt, dass, obwohl der GC mit zirkulären Referenzen umgehen kann, er (manchmal) Probleme mit zirkulären Event-Handler-Referenzen hat. IE Objekt1 Referenzen Objekt2 und Objekt1 hat eine Methode, die behandelt und Ereignis von Objekt2. Ich habe Umstände gefunden, in denen die Objekte nicht freigegeben wurden, als ich es erwartet hatte, aber ich konnte sie nie in einem Testfall reproduzieren.


Die Garbage Collection funktioniert durch Verfolgen von Anwendungswurzeln. Anwendungsstämme sind Speicherorte, die Verweise auf Objekte auf dem verwalteten Heap (oder auf null) enthalten. In .NET sind Roots

  1. Verweise auf globale Objekte
  2. Verweise auf statische Objekte
  3. Verweise auf statische Felder
  4. Referenzen auf dem Stapel zu lokalen Objekten
  5. Verweise auf dem Stapel auf Objektparameter, die an Methoden übergeben werden
  6. Verweise auf Objekte, die auf ihre Fertigstellung warten
  7. Verweise in CPU-Registern auf Objekte auf dem verwalteten Heap

Die Liste der aktiven Wurzeln wird von der CLR verwaltet. Der Garbage Collector betrachtet die Objekte auf dem verwalteten Heap und sieht, auf welche die Anwendung immer noch Zugriff hat, dh über einen Anwendungsstamm erreichbar ist. Ein solches Objekt gilt als verwurzelt.

Angenommen, Sie haben ein übergeordnetes Formular, das Verweise auf untergeordnete Formulare enthält, und diese untergeordneten Formulare enthalten Verweise auf das übergeordnete Formular. Nehmen Sie weiterhin an, dass die Anwendung keine Verweise auf das übergeordnete Element oder eines der untergeordneten Formulare enthält. Für die Zwecke des Garbage Collectors werden diese verwalteten Objekte dann nicht mehr verwurzelt und bei der nächsten Garbage-Collection als Garbage Collection behandelt.


Ich möchte Vilx 'Bemerkung über Ereignisse wiederholen und ein Designmuster empfehlen, das dabei hilft, das Problem anzugehen.

Nehmen wir an, Sie haben einen Typ, der eine Ereignisquelle ist, z. B .:

interface IEventSource
{
    event EventHandler SomethingHappened;
}

Hier ist ein Ausschnitt einer Klasse, die Ereignisse von Instanzen dieses Typs behandelt. Wenn Sie der Eigenschaft eine neue Instanz zuweisen, entfernen Sie zunächst die vorherige Zuordnung und abonnieren dann die neue Instanz. Die Nulltests stellen korrekte Grenzverhaltensweisen sicher und erleichtern die Entsorgung, indem Sie nur die Eigenschaft null setzen.

Das bringt den Punkt der Entsorgung ans Licht. Jede Klasse, die Ereignisse abonniert, sollte die IDisposable-Schnittstelle implementieren, da Ereignisse verwaltete Ressourcen sind. (NB: Ich habe eine kurze Implementierung des Dispose-Musters aus Gründen der Kürze im Beispiel übersprungen, aber Sie bekommen die Idee.)

class MyClass : IDisposable
{
    IEventSource m_EventSource;
    public IEventSource EventSource
    {
        get { return m_EventSource; }
        set
        {
            if( null != m_EventSource )
            {
                m_EventSource -= HandleSomethingHappened;
            }
            m_EventSource = value;
            if( null != m_EventSource )
            {
                m_EventSource += HandleSomethingHappened;
            }
        }
    }

    public Dispose()
    {
        EventSource = null;
    }

    // ...
}

Wenn sowohl das Eltern- als auch das Kind-Objekt nicht referenziert werden, sondern sich nur gegenseitig referenzieren, erhalten sie GCed.

Holen Sie sich einen Speicher-Profiler, um Ihre Anwendung wirklich zu überprüfen und alle Ihre Fragen zu beantworten. Ich kann http://memprofiler.com/ empfehlen





circular-dependency