web-dev-qa-db-de.com

Speicherverlust in C #

Ist es in einem verwalteten System immer möglich, Speicher zu verlieren, wenn Sie sicherstellen, dass alle Handles, die IDispose implementieren, verworfen werden? 

Gibt es Fälle, in denen einige Variablen ausgelassen werden?

54
Joan Venge

Ereignishandler sind eine sehr häufige Quelle für nicht offensichtliche Speicherverluste. Wenn Sie ein Ereignis für object1 von object2 abonnieren, dann object2.Dispose () ausführen und so tun, als sei es nicht vorhanden (und alle Verweise aus Ihrem Code löschen), gibt es in object1 einen impliziten Verweis, der verhindert, dass object2 verwendet wird Müll gesammelt.

MyType object2 = new MyType();

// ...
object1.SomeEvent += object2.myEventHandler;
// ...

// Should call this
// object1.SomeEvent -= object2.myEventHandler;

object2.Dispose();

Dies ist ein häufiger Fall eines Lecks - es wird vergessen, Ereignisse leicht abbestellen zu können. Wenn object1 gesammelt wird, wird object2 natürlich auch gesammelt, aber erst dann.

63
Reed Copsey

Ich glaube nicht, dass Speicherlecks im C++ - Stil möglich sind. Der Müllsammler sollte diese berücksichtigen. Es ist möglich, ein statisches Objekt zu erstellen, das Objektreferenzen zusammenfasst, obwohl die Objekte nie wieder verwendet werden. Etwas wie das:

public static class SomethingFactory
{
    private static List<Something> listOfSomethings = new List<Something>();

    public static Something CreateSomething()
    {
        var something = new Something();
        listOfSomethings.Add(something);
        return something;
    }
}

Dies ist ein offensichtlich dummes Beispiel, aber es entspricht einem Speicherverlust bei einer verwalteten Laufzeit.

40
Michael Meadows

Andere haben darauf hingewiesen, dass Klassen, die keine nicht verwalteten Ressourcen verwenden, keinen Speicher verlieren, solange es keinen tatsächlichen Fehler im Speichermanager gibt.

Was Sie in .NET sehen, sind keine Speicherverluste, sondern Objekte, die niemals entsorgt werden. Ein Objekt wird nicht entsorgt, solange der Speicherbereiniger es im Objektdiagramm finden kann. Wenn also irgendein lebendes Objekt irgendwo einen Bezug zu dem Objekt hat, wird es nicht entsorgt.

Event-Registrierung ist eine gute Möglichkeit, dies zu erreichen. Wenn sich ein Objekt für ein Ereignis registriert, hat das, womit es registriert ist, einen Verweis darauf, und selbst wenn Sie jeden anderen Verweis auf das Objekt entfernen, bleibt es bis zum Aufheben der Registrierung (oder das Objekt, mit dem es registriert ist) unerreichbar.

Sie müssen also auf Objekte achten, die sich ohne Ihr Wissen für statische Ereignisse registrieren. Ein geschicktes Feature von ToolStrip ist zum Beispiel, dass, wenn Sie Ihr Display-Design ändern, dieses automatisch im neuen Design angezeigt wird. Es erledigt diese raffinierte Funktion, indem es sich für das statische SystemEvents.UserPreferenceChanged-Ereignis registriert. Wenn Sie Ihr Windows-Design ändern, wird das Ereignis ausgelöst und alle ToolStrip-Objekte, die das Ereignis abhören, werden benachrichtigt, wenn ein neues Design vorhanden ist.

Okay, nehmen Sie an, Sie entscheiden sich dafür, eine ToolStrip in Ihrem Formular wegzuwerfen:

private void DiscardMyToolstrip()
{
    Controls.Remove("MyToolStrip");
}

Sie haben jetzt eine ToolStrip, die niemals sterben wird. Auch wenn es sich nicht mehr in Ihrem Formular befindet, wird Windows jedes Mal, wenn der Benutzer das Thema ändert, pflichtbewusst die ansonsten nicht vorhandene ToolStrip-Datei darüber informieren. Jedes Mal, wenn der Garbage Collector ausgeführt wird, denkt er: "Ich kann das Objekt nicht wegwerfen, das UserPreferenceChanged-Ereignis verwendet es."

Das ist kein Speicherleck. Aber es könnte genauso gut sein.

Solche Dinge machen einen Memory-Profiler von unschätzbarem Wert. Führen Sie einen Speicher-Profiler aus, und Sie sagen: "Das ist seltsam, es scheinen zehntausend ToolStrip-Objekte auf dem Heap zu sein, auch wenn sich in meinem Formular nur eines befindet. Wie ist das passiert?"

Oh, und falls Sie sich fragen, warum manche Leute denken, dass Eigenschaftssetzer böse sind: Um eine ToolStrip-Registrierung vom UserPreferenceChanged-Ereignis aufzuheben, setzen Sie die Visible-Eigenschaft auf false.

27
Robert Rossney

Delegierte können zu nicht intuitiven Speicherverlusten führen.

Wenn Sie einen Delegaten aus einer Instanzmethode erstellen, wird ein Verweis auf diese Instanz "in" diesem Delegaten gespeichert.

Wenn Sie mehrere Delegaten zu einem Multicast-Delegaten zusammenfassen, haben Sie außerdem einen großen Fleck von Verweisen auf zahlreiche Objekte, die keine Abfälle sammeln, solange der Multicast-Delegierte irgendwo verwendet wird.

20

Wenn Sie eine WinForms-Anwendung entwickeln, ist die Eigenschaft Control.AllowDrop ein subtiles "Leck" (wird zum Aktivieren von Drag & Drop verwendet). Wenn AllowDrop auf "true" gesetzt ist, bleibt die CLR über einen System.Windows.Forms.DropTarget an Ihrem Control erhalten. Um dies zu beheben, stellen Sie sicher, dass die Control-Eigenschaft der Variable AllowDrop auf false gesetzt ist, wenn Sie sie nicht mehr benötigen. Die CLR kümmert sich um den Rest.

17
Zach Johnson

Der einzige Grund für Speicherverluste in der .NET-Anwendung ist, dass Objekte immer noch referenziert werden, obwohl ihre Lebensdauer beendet ist. Daher kann der Müllsammler sie nicht sammeln. Und sie werden zu langlebigen Objekten.

Ich finde, dass es sehr einfach ist, ein Leck durch Abonnieren von Ereignissen zu verursachen, ohne es abzubrechen, wenn die Lebensdauer des Objekts endet.

7
tranmq

Wie bereits erwähnt, wird das Speichern von Verweisen im Laufe der Zeit zu einem zunehmenden Speicherbedarf führen. Ein einfacher Weg, um in diese Situation zu gelangen, sind Ereignisse. Wenn Sie ein langlebiges Objekt mit einem Ereignis hatten, das Ihre anderen Objekte abhören, und wenn die Listener niemals entfernt werden, wird das Ereignis für das langlebige Objekt diese anderen Instanzen lange am Leben erhalten, nachdem sie nicht mehr benötigt werden.

7
toad

Sie finden meinen neuen Artikel möglicherweise nützlich: So können Sie Speicher- und Ressourcenlecks in .NET-Anwendungen erkennen und vermeiden

6
Fabrice

Reflexion emittieren ist eine weitere potentielle Quelle von Lecks, z. integrierte Objekt-Deserialisierer und ausgefallene SOAP/XML-Clients. Zumindest in früheren Versionen des Frameworks wurde generierter Code in abhängigen AppDomains nie entladen ...

5
Pontus Gagge

Es ist ein Mythos, dass Sie keinen Speicherplatz in verwaltetem Code verlieren können. Zugegeben, es ist viel schwieriger als in nicht verwaltetem C++, aber es gibt eine Million Möglichkeiten, dies zu tun. Statische Objekte mit Referenzen, unnötigen Verweisen, Zwischenspeichern usw. Wenn Sie die Dinge auf "richtige" Art und Weise ausführen, werden viele Ihrer Objekte erst viel später als nötig gesammelt. auf praktische und nicht theoretische Weise.

Glücklicherweise gibt es Tools, die Ihnen helfen können. Ich benutze häufig den CLR Profiler von Microsoft - es ist nicht das benutzerfreundlichste Tool, das jemals geschrieben wurde, aber es ist definitiv sehr nützlich und es ist kostenlos.

4
Tamas Czinege

Sobald alle Verweise auf ein Objekt verschwunden sind, gibt der Speicherbereiniger das Objekt beim nächsten Durchlauf frei. Ich würde nicht sagen, dass es unmöglich ist, Speicher auszuliefern, aber es ist ziemlich schwierig. Um auszutreten, müsste man einen Verweis auf ein Objekt haben, das herumsteht, ohne es zu merken.

Zum Beispiel, wenn Sie Objekte in einer Liste instanziieren und dann vergessen, sie aus der Liste zu entfernen, wenn Sie fertig sind, und vergessen Sie nicht, sie zu entsorgen.

2
Kevin Laity

Es können Leckagen auftreten, wenn nicht verwaltete Ressourcen nicht ordnungsgemäß bereinigt werden. Klassen, die IDisposable implementieren, können auslaufen.

Für reguläre Objektverweise ist jedoch keine explizite Speicherverwaltung erforderlich, wie dies bei untergeordneten Sprachen der Fall ist.

1
Ben S

Nicht wirklich ein Speicherverlust, aber bei großen Objekten (bei mehr als 64 KB, wenn ich mich recht erinnere) ist es leicht, zu wenig Speicher zu haben. Sie werden im LOH gespeichert und das ist NICHT defragmentiert. Durch die Verwendung dieser großen Objekte und deren Freigabe wird der Speicher auf dem LOH freigegeben, der freie Speicher wird jedoch nicht mehr von der .NET-Laufzeitumgebung für diesen Prozess verwendet. Auf diese Weise können Sie den Platz auf dem LOH leicht verlassen, indem Sie nur wenige große Objekte auf dem LOH verwenden. Dieses Problem ist Microsoft bekannt, aber da ich mich erinnere, wird derzeit eine Lösung dafür geplant.

1

Eine Erinnerung an sich selbst So finden Sie Memory Leak:

  • Entfernen Sie und gc.collect-Aufrufe. 
  • Warten Sie, bis der Speicher sicher ist.
  • Erstellen Sie eine Sicherungsdatei vom Task-Manager.
  • Öffnen Sie die Dump-Dateien mit DebugDiag .
  • Analysieren Sie zuerst das Ergebnis. Das Ergebnis von dort sollte uns helfen, was normalerweise den meisten Speicherplatz beansprucht. 
  • Korrigieren Sie den Code, bis dort kein Speicherverlust gefunden wird.
  • Verwenden Sie eine Drittanbieteranwendung wie .net Profiler. (Wir können eine Testversion verwenden, müssen aber das Problem so schnell wie möglich beheben. Der erste Speicherabzug sollte uns vor allem helfen, wie das Durchsickern funktioniert.)
  • Wenn sich das Problem im virtuellen Speicher befindet, müssen Sie den nicht verwalteten Speicher überwachen. (Normalerweise gibt es eine andere Konfiguration, die aktiviert werden muss.)
  • Führen Sie eine Drittanbieteranwendung basierend auf ihrer Verwendung aus. 

Häufiges Speicherleckproblem:

  • Ereignisse/Delegierte werden niemals entfernt. (Vergewissern Sie sich beim Entsorgen, dass das Ereignis nicht registriert ist) - siehe ReepChopsey answer
  • Liste/Wörterbuch wurden nie gelöscht.
  • Ein Objekt, das auf ein anderes Objekt verweist, das im Speicher gespeichert wird, wird niemals entsorgt. (Klonen Sie es für eine einfachere Verwaltung)
0
Kosmas

Wenn es sich um einen Speicherverlust handelt, könnte dies auch mit dieser Art von Code erreicht werden:

public class A
{
    B b;
    public A(B b) { this.b = b; }
    ~A()
    {
        b = new B();
    }
}

public class B
{
    A a;
    public B() { this.a = new A(this); }
    ~B()
    {
        a = new A(this);
    }
}

class Program
{
    static void Main(string[] args)
    {
        {
            B[] toBeLost = new B[100000000];
            foreach (var c in toBeLost)
            {
                toBeLost.ToString(); //to make JIT compiler run the instantiation above
            }
        }
        Console.ReadLine();
    }
}
0
meJustAndrew

Kleine Funktionen helfen dabei, "Speicherlecks" zu vermeiden. Weil der Garbage Collector am Ende von Funktionen lokale Variablen freigibt. Wenn die Funktion groß ist und viel Speicher beansprucht, müssen Sie selbst lokale Variablen freigeben, die viel Speicher benötigen und nicht mehr benötigt werden. Gleiche globale Variablen (Arrays, Listen) sind ebenfalls schlecht.

Bei C # kam es zu Speicherverlusten, wenn Bilder erstellt und nicht entsorgt wurden. Welches ist ein bisschen seltsam. Die Leute sagen, Sie müssen .Dispose () für jedes Objekt aufrufen, das es hat. Die Dokumentation für grafische C # -Funktionen erwähnt dies jedoch nicht immer, beispielsweise für die Funktion GetThumbnailImage (). C # -Compiler sollte Sie darüber warnen, denke ich.

0
Tone Škoda

Bei Verwendung von .NET XmlSerializer kann ein Speicherverlust auftreten, da nicht verwalteter Code verwendet wird, der nicht entfernt wird.

Lesen Sie die Dokumente und suchen Sie auf dieser Seite nach "Memory Leak":

https://docs.Microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer?view=netframework-4.7.2

0
FrankyHollywood

Bei meinem letzten Job verwendeten wir eine .NET SQLite-Bibliothek eines Drittanbieters, die wie ein Sieb durchsickerte.

Wir haben viele schnelle Dateneinfügungen in einer seltsamen Situation vorgenommen, in der die Datenbankverbindung jedes Mal geöffnet und geschlossen werden musste. Die 3rd Party-Bibliothek hat einige der gleichen Verbindungsöffnungen ausgeführt, die wir manuell ausführen sollten, und hat sie nicht dokumentiert. Es enthielt auch die Referenzen, die wir nie gefunden haben. Das Ergebnis war, dass 2x so viele Verbindungen geöffnet wurden, wie eigentlich nur sein sollte, und nur 1/2 geschlossen wurde. Und da die Referenzen gehalten wurden, hatten wir ein Gedächtnisleck.

Dies ist offensichtlich nicht dasselbe wie ein klassisches C/C++ - Speicherverlust, aber es war für uns eine Absicht.

0
Dinah

Während es möglich ist, dass etwas in dem Framework ein Leck aufweist, ist es wahrscheinlich, dass Sie etwas haben, das nicht ordnungsgemäß entsorgt wird, oder etwas den GC daran hindert, es zu entsorgen. IIS wäre eine Primzahl Kandidat dafür. 

Denken Sie daran, dass nicht alles in .NET vollständig verwalteter Code, COM-Interop, Datei-Io wie Dateistreams, DB-Anforderungen, Bilder usw. ist. 

Ein Problem, das wir vor einiger Zeit hatten (.net 2.0 auf IIS 6), bestand darin, dass wir ein Image erstellen und dann entsorgen würden, aber IIS den Speicher für eine Weile nicht freigeben würde .

0
Bob The Janitor

Die einzigen Lecks (abgesehen von Fehlern in der Laufzeit, die möglicherweise vorhanden sind, jedoch nicht sehr wahrscheinlich aufgrund der Speicherbereinigung), beziehen sich auf native Ressourcen. Wenn Sie P/Invoke in eine systemeigene Bibliothek öffnen, in der Dateihandles oder Socketverbindungen oder was auch immer im Namen Ihrer verwalteten Anwendung geöffnet wird, und Sie diese niemals explizit schließen (und nicht in einem Disposer oder Destruktor/Finalizer behandeln), können Sie dies tun haben Speicher- oder Ressourcenlecks, da die Laufzeitumgebung nicht alle automatisch für Sie verwalten kann.

Wenn Sie sich jedoch an rein verwaltete Ressourcen halten, sollten Sie gut damit umgehen. Wenn bei Ihnen irgendeine Form von Speicherverlust auftritt, ohne nativen Code aufzurufen, ist dies ein Fehler.

0
Michael Trausch

Erstellen Sie in einer Console- oder Win-App ein Panel-Objekt (panel1) und fügen Sie dann 1000 PictureBox hinzu, wobei die Image-Eigenschaft festgelegt ist, und rufen Sie dann panel1.Controls.Clear auf. Alle PictureBox-Steuerelemente befinden sich noch im Speicher, und GC kann sie nicht sammeln:

var panel1 = new Panel();
var image = Image.FromFile("image/heavy.png");
for(var i = 0; i < 1000;++i){
  panel1.Controls.Add(new PictureBox(){Image = image});
}
panel1.Controls.Clear(); // => Memory Leak!

Der richtige Weg wäre es 

for (int i = panel1.Controls.Count-1; i >= 0; --i)
   panel1.Controls[i].Dispose();

Speicherlecks beim Aufruf von Controls.Clear ()

Durch Aufrufen der Clear-Methode werden keine Steuerpunkte aus dem Speicher entfernt . Sie müssen die Dispose-Methode explizit aufrufen, um Speicherverluste zu vermeiden

0
Daniel B