33

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 kommen Müll-Sammlung Zeit:Kreisreferenzen Ursache Speicherleck?

Parent hat einen Verweis auf das untergeordnete Formular, über die Steuerelemente Sammlung (Kind Formular ist dort eingebettet). Kinderform ist nicht GC'd.

Untergeordnetes Formular hat einen Verweis auf das übergeordnete Formular über das Feld für private Mitglieder. 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"?

Antwort

36

Große Frage!

Nein, beide Formulare werden GCs (können) sein, weil der GC nicht direkt nach Referenzen in anderen Referenzen sucht. Es sucht nur nach so genannten "Root" -Referenzen ... Dies beinhaltet Referenzvariablen auf dem Stack, (Variable ist auf dem Stack, tatsächliches Objekt ist natürlich auf dem Heap), Referenzvariablen in CPU-Registern und Referenzvariablen, die sind Statische Felder in Klassen ...

Auf alle anderen Referenzvariablen wird nur zugegriffen (und GC'd), wenn sie in einer Eigenschaft eines der "root" -Referenzobjekte referenziert werden, die durch den obigen Prozess gefunden wurden (oder in einem Objekt, auf das durch eine Referenz in einem Wurzelobjekt usw. verwiesen wird ...)

Also nur wenn eines der Formulare irgendwo in einer "root" -Referenz referenziert wird - Dann sind beide Formulare vor dem GC sicher.

Nur so kann ich mir vorstellen, es zu "beweisen", (ohne Verwendung von Speicher-Trace-Utilities) wäre, ein paar hunderttausend dieser Formen in einer Schleife innerhalb einer Methode zu erstellen, dann in der Methode zu betrachten den Speicherbedarf der App, verlassen Sie dann die Methode, rufen Sie den GC auf und sehen Sie sich den Footprint erneut an.

+2

Oder einfach einen massiven Puffer in jedem Formular zuweisen. – Gusdor

5

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

Erhalten Sie einen Speicherprofiler, um Ihre Anwendung wirklich zu überprüfen und alle Ihre Fragen zu beantworten. Ich kann empfehlen http://memprofiler.com/

0

Der GC kann korrekt mit zirkulären Referenzen umgehen und wenn diese Referenzen die einzigen Dinge waren, die das Formular am Leben erhalten, dann würden sie gesammelt werden.
Ich hatte viele Probleme mit .net nicht Speicher von Formularen zurückfordern. 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 eine 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, scheint es Probleme (manchmal) mit zirkulären Ereignishandler Referenzen.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.

16

Wie andere schon gesagt haben, hat GC keine Probleme mit zirkulären Referenzen. Ich möchte nur hinzufügen, dass ein gewöhnlicher Ort zum Lecken von Speicher in .NET Event-Handler sind. Wenn eines Ihrer Formulare einen angefügten Ereignishandler an ein anderes Objekt hat, das "lebendig" ist, dann gibt es einen Verweis auf Ihr Formular und das Formular wird nicht gecodiert.

+2

Eine Antwort, die Ihr Beispiel beschreibt: http://stackoverflow.com/a/12133788/6345 –

12

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 Wurzeln sind

  1. Verweise auf globale Objekte
  2. Verweise auf statische Objekte
  3. Verweise auf statische Felder
  4. Referenzen auf dem Stapel auf lokale Objekte
  5. Referenzen auf den Stapel Parameter zum Objekt an Methoden übergeben
  6. Verweise auf Objekte, die auf Finalisierung warten
  7. Referenzen in CPU-Registern zu Objekten auf dem verwalteten er ap

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.

+0

@Jason, was meinst du mit einem "Objekt-Parameter"? Und ich glaube, die Position der Referenz ist die entscheidende Determinante ... Wenn auf dem Stapel, oder ein statisches Mitglied einer Klasse oder in einer CPU-Register, dann ist es eine Stammreferenz. ... sonst nicht. (außer freachable queue, - ein anderes Thema) –

2

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

Lassen Sie uns sagen, dass Sie eine Art, die eine Ereignisquelle ist, z.B .:

interface IEventSource 
{ 
    event EventHandler SomethingHappened; 
} 

Hier ist ein Ausschnitt aus 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.

Welches bringt den Punkt der Entsorgung. Jede Klasse, die Ereignisse abonniert, sollte die IDisposable-Schnittstelle implementieren, da Ereignisse verwaltete Ressourcen sind. (N.B. Ich übersprang eine kurze Implementierung des Dispose-Musters in dem Beispiel der Kürze halber, aber Sie erhalten 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; 
    } 

    // ... 
}