Anatomie eines „Gedächtnislecks“

172

In der .NET-Perspektive:

  • Was ist ein Speicherverlust ?
  • Wie können Sie feststellen, ob Ihre Anwendung undicht ist? Was sind die Auswirkungen?
  • Wie können Sie einen Speicherverlust verhindern?
  • Wenn Ihre Anwendung einen Speicherverlust aufweist, verschwindet dieser, wenn der Prozess beendet oder beendet wird? Oder wirken sich Speicherverluste in Ihrer Anwendung auch nach Abschluss des Prozesses auf andere Prozesse im System aus?
  • Und was ist mit nicht verwaltetem Code, auf den über COM Interop und / oder P / Invoke zugegriffen wird?
huseyint
quelle

Antworten:

110

Die beste Erklärung, die ich gesehen habe, ist in Kapitel 7 des kostenlosen E-Books Grundlagen der Programmierung .

Grundsätzlich tritt in .NET ein Speicherverlust auf, wenn referenzierte Objekte gerootet werden und daher kein Müll gesammelt werden kann. Dies tritt versehentlich auf, wenn Sie an Referenzen festhalten, die über den beabsichtigten Bereich hinausgehen.

Sie werden wissen, dass Sie Lecks haben, wenn Sie OutOfMemoryExceptions erhalten oder Ihre Speichernutzung über das hinausgeht, was Sie erwarten ( PerfMon hat nette Speicherzähler).

Das Verständnis des Speichermodells von .NET ist der beste Weg, dies zu vermeiden. Um zu verstehen, wie der Garbage Collector funktioniert und wie Referenzen funktionieren, verweise ich Sie erneut auf Kapitel 7 des E-Books. Denken Sie auch an häufige Fallstricke, wahrscheinlich die häufigsten Ereignisse. Wenn Objekt A für ein Ereignis auf Objekt B registriert ist, bleibt Objekt A so lange bestehen, bis Objekt B verschwindet, da B einen Verweis auf A enthält . Die Lösung besteht darin, die Registrierung Ihrer Ereignisse aufzuheben, wenn Sie fertig sind.

Mit einem guten Speicherprofil können Sie natürlich Ihre Objektdiagramme anzeigen und die Verschachtelung / Referenzierung Ihrer Objekte untersuchen, um festzustellen, woher Referenzen stammen und welches Stammobjekt dafür verantwortlich ist ( Red-Gate-Ameisenprofil , JetBrains dotMemory, memprofiler sind wirklich gut) Sie können auch WinDbg und SOS nur mit Text verwenden. Ich würde jedoch dringend ein kommerzielles / visuelles Produkt empfehlen, es sei denn, Sie sind ein echter Guru.

Ich glaube, dass nicht verwalteter Code seinen typischen Speicherlecks unterliegt, außer dass gemeinsam genutzte Referenzen vom Garbage Collector verwaltet werden. In diesem letzten Punkt könnte ich mich irren.

Karl Seguin
quelle
11
Oh du magst ein Buch, oder? Ich habe gesehen, wie der Autor von Zeit zu Zeit im Stapelüberlauf auftauchte.
Johnno Nolan
Einige .NET-Objekte können sich auch selbst rooten und uneinbringlich werden. Alles, was IDisposable ist, sollte aus diesem Grund entsorgt werden.
Kyoryu
1
@kyoryu: Wie wurzelt ein Objekt selbst?
Andrei Rinea
2
@Andrei: Ich denke, ein laufender Thread ist vielleicht das beste Beispiel für ein Objekt, das sich selbst verwurzelt. Ein Objekt, das an einem statischen, nicht öffentlichen Ort auf sich selbst verweist (z. B. ein statisches Ereignis abonniert oder einen Singleton durch statische Feldinitialisierung implementiert), hat sich möglicherweise selbst verwurzelt, da es keinen offensichtlichen Weg gibt, um ... "entwurzeln" es von seinem Liegeplatz.
Jeffrey Hantin
@ Jeffrey Dies ist eine unkonventionelle Art zu beschreiben, was passiert und ich mag es!
Exitos
35

Genau genommen verbraucht ein Speicherverlust Speicher, der vom Programm "nicht mehr verwendet" wird.

"Nicht mehr verwendet" hat mehr als eine Bedeutung, es könnte "kein Verweis mehr darauf" bedeuten, dh völlig nicht wiederherstellbar, oder es könnte bedeuten, referenziert, wiederherstellbar, nicht verwendet, aber das Programm behält die Verweise trotzdem bei. Nur das letztere gilt für .Net für perfekt verwaltete Objekte . Es sind jedoch nicht alle Klassen perfekt, und irgendwann kann eine zugrunde liegende nicht verwaltete Implementierung dauerhaft Ressourcen für diesen Prozess verlieren.

In allen Fällen verbraucht die Anwendung mehr Speicher als unbedingt erforderlich. Die Nebenwirkungen können, abhängig von der Menge, die durchgesickert ist, von keiner über eine durch übermäßiges Sammeln verursachte Verlangsamung bis zu einer Reihe von Speicherausnahmen und schließlich zu einem schwerwiegenden Fehler, gefolgt von einer erzwungenen Prozessbeendigung, reichen.

Sie wissen, dass eine Anwendung ein Speicherproblem hat, wenn die Überwachung zeigt, dass Ihrem Prozess nach jedem Speicherbereinigungszyklus immer mehr Speicher zugewiesen wird . In diesem Fall behalten Sie entweder zu viel Speicher oder eine zugrunde liegende nicht verwaltete Implementierung ist undicht.

Bei den meisten Lecks werden Ressourcen wiederhergestellt, wenn der Prozess beendet wird. Einige Ressourcen werden jedoch in bestimmten Fällen nicht immer wiederhergestellt. GDI-Cursor-Handles sind dafür berüchtigt. Wenn Sie über einen Interprozess-Kommunikationsmechanismus verfügen, wird der im anderen Prozess zugewiesene Speicher natürlich erst freigegeben, wenn dieser Prozess ihn freigibt oder beendet.

Münze
quelle
32

Ich denke, die Fragen "Was ist ein Speicherverlust?" Und "Was sind die Auswirkungen?" Wurden bereits gut beantwortet, aber ich wollte den anderen Fragen noch ein paar Dinge hinzufügen ...

So verstehen Sie, ob Ihre Anwendung undicht ist

Eine interessante Möglichkeit besteht darin, perfmon zu öffnen und Spuren für # Bytes in allen Heaps und # Gen 2-Sammlungen hinzuzufügen , wobei jeweils nur Ihr Prozess betrachtet wird. Wenn das Ausüben eines bestimmten Features dazu führt, dass sich die Gesamtzahl der Bytes erhöht und dieser Speicher nach der nächsten Gen 2-Sammlung zugewiesen bleibt, kann man sagen, dass das Feature Speicher verliert.

Wie man etwas vorbeugt

Andere gute Meinungen wurden abgegeben. Ich möchte nur hinzufügen, dass die am häufigsten übersehene Ursache für .NET-Speicherlecks darin besteht, Objekten Ereignishandler hinzuzufügen, ohne sie zu entfernen. Ein an ein Objekt angehängter Ereignishandler ist eine Art Referenz auf dieses Objekt und verhindert so die Erfassung, selbst nachdem alle anderen Referenzen verschwunden sind. Denken Sie immer daran, Ereignishandler zu trennen (unter Verwendung der -=Syntax in C #).

Verschwindet das Leck, wenn der Prozess beendet wird, und was ist mit COM Interop?

Wenn Ihr Prozess beendet wird, wird der gesamte Speicher, der seinem Adressraum zugeordnet ist, vom Betriebssystem zurückgefordert, einschließlich aller COM-Objekte, die von DLLs bereitgestellt werden. COM-Objekte können vergleichsweise selten aus separaten Prozessen bedient werden. In diesem Fall sind Sie beim Beenden Ihres Prozesses möglicherweise weiterhin für den in den von Ihnen verwendeten COM-Server-Prozessen zugewiesenen Speicher verantwortlich.

Martin
quelle
19

Ich würde Speicherlecks als ein Objekt definieren, das nicht den gesamten nach dem Abschluss zugewiesenen Speicher freigibt. Ich habe festgestellt, dass dies in Ihrer Anwendung passieren kann, wenn Sie Windows-API und COM (dh nicht verwalteten Code, der einen Fehler enthält oder nicht ordnungsgemäß verwaltet wird), im Framework und in Komponenten von Drittanbietern verwenden. Ich habe auch festgestellt, dass das Problem nicht verursacht werden kann, nachdem bestimmte Objekte wie Stifte verwendet wurden.

Ich persönlich habe Ausnahmen aufgrund von Speichermangel erlitten, die verursacht werden können, aber nicht ausschließlich für Speicherlecks in Dot-Net-Anwendungen gelten. (OOM kann auch vom Pinning stammen, siehe Pinning Artical ). Wenn Sie keine OOM-Fehler erhalten oder bestätigen müssen, ob ein Speicherverlust dies verursacht, können Sie Ihre Anwendung nur profilieren.

Ich würde auch versuchen, Folgendes sicherzustellen:

a) Alles, was Idisposable implementiert, wird entweder mit einem finally-Block oder mit der using-Anweisung entsorgt, zu der Pinsel, Stifte usw. gehören (einige Leute argumentieren, alles zusätzlich auf nichts zu setzen).

b) Alles, was eine close-Methode hat, wird mit finally oder der using-Anweisung wieder geschlossen (obwohl ich festgestellt habe, dass using nicht immer geschlossen wird, je nachdem, ob Sie das Objekt außerhalb der using-Anweisung deklariert haben).

c) Wenn Sie nicht verwaltete Code- / Windows-APIs verwenden, werden diese anschließend korrekt behandelt. (Einige haben Bereinigungsmethoden zum Freigeben von Ressourcen)

Hoffe das hilft.

John
quelle
19

Wenn Sie einen Speicherverlust in .NET diagnostizieren müssen, überprüfen Sie diese Links:

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

In diesen Artikeln wird beschrieben, wie Sie einen Speicherauszug Ihres Prozesses erstellen und analysieren, damit Sie zunächst feststellen können, ob Ihr Leck nicht verwaltet oder verwaltet wird, und ob es verwaltet wird und wie Sie herausfinden, woher es stammt.

Microsoft hat auch ein neueres Tool namens DebugDiag, das beim Generieren von Crash-Dumps hilft und ADPlus ersetzt.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=de

Eric Z Bart
quelle
15

Die beste Erklärung für die Funktionsweise des Garbage Collectors findet sich in Jeff Richters CLR über das C # -Buch (Kap. 20). Das Lesen bietet eine gute Grundlage für das Verständnis, wie Objekte bestehen bleiben.

Eine der häufigsten Ursachen für das versehentliche Rooten von Objekten ist das Anschließen von Ereignissen außerhalb einer Klasse. Wenn Sie ein externes Ereignis anschließen

z.B

SomeExternalClass.Changed += new EventHandler(HandleIt);

und vergessen Sie, es zu entsperren, wenn Sie entsorgen, dann hat SomeExternalClass einen Verweis auf Ihre Klasse.

Wie oben erwähnt, zeigt der SciTech-Speicherprofiler hervorragend Wurzeln von Objekten an, von denen Sie vermuten, dass sie undicht sind.

Es gibt aber auch eine sehr schnelle Möglichkeit, einen bestimmten Typ zu überprüfen, indem Sie einfach WnDBG verwenden (Sie können dies sogar im VS.NET-Sofortfenster verwenden, während Sie angehängt sind):

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

Tun Sie jetzt etwas, von dem Sie glauben, dass es die Objekte dieses Typs entsorgt (z. B. ein Fenster schließen). Hier ist es praktisch, irgendwo eine Debug-Schaltfläche zu haben, die System.GC.Collect()ein paar Mal ausgeführt wird.

Dann !dumpheap -stat -type <TypeName>wieder laufen . Wenn die Zahl nicht oder nicht so stark gesunken ist, wie Sie es erwartet haben, haben Sie eine Grundlage für weitere Untersuchungen. (Ich habe diesen Tipp von einem Seminar von erhalten Ingo Rammer erhalten ).

Gus Paul
quelle
14

Ich denke, in einer verwalteten Umgebung wäre ein Leck, wenn Sie einen unnötigen Verweis auf einen großen Speicherblock behalten würden.

Bernard
quelle
11

Warum denken die Leute, dass ein Speicherverlust in .NET nicht mit einem anderen Verlust identisch ist?

Ein Speicherverlust tritt auf, wenn Sie eine Verbindung zu einer Ressource herstellen und diese nicht loslassen. Sie können dies sowohl in verwalteter als auch in nicht verwalteter Codierung tun.

In Bezug auf .NET und andere Programmiertools gab es Ideen zur Speicherbereinigung und andere Möglichkeiten zur Minimierung von Situationen, die zu einem Leck Ihrer Anwendung führen. Die beste Methode zur Verhinderung von Speicherlecks besteht jedoch darin, dass Sie Ihr zugrunde liegendes Speichermodell und die Funktionsweise auf der von Ihnen verwendeten Plattform verstehen müssen.

Zu glauben, dass GC und andere Magie Ihr Chaos beseitigen, ist der kurze Weg zu Speicherlecks und wird später schwer zu finden sein.

Wenn Sie die Codierung nicht verwalten, stellen Sie normalerweise sicher, dass Sie bereinigen. Sie wissen, dass die Ressourcen, die Sie in Anspruch nehmen, in Ihrer Verantwortung für die Bereinigung liegen und nicht für die des Hausmeisters.

In .NET hingegen denken viele Leute, dass der GC alles bereinigen wird. Nun, es tut etwas für Sie, aber Sie müssen sicherstellen, dass es so ist. .NET verpackt viele Dinge, sodass Sie nicht immer wissen, ob es sich um eine verwaltete oder nicht verwaltete Ressource handelt, und Sie müssen sicherstellen, womit Sie es zu tun haben. Der Umgang mit Schriftarten, GDI-Ressourcen, Active Directory, Datenbanken usw. ist normalerweise wichtig.

In verwalteten Begriffen werde ich meinen Hals auf die Linie legen, um zu sagen, dass er verschwindet, sobald der Prozess beendet / entfernt wird.

Ich sehe jedoch, dass viele Leute dies haben, und ich hoffe wirklich, dass dies enden wird. Sie können den Benutzer nicht bitten, Ihre App zu beenden, um Ihr Chaos zu beseitigen! Schauen Sie sich einen Browser an, der IE, FF usw. sein kann, öffnen Sie beispielsweise Google Reader, lassen Sie ihn einige Tage lang stehen und sehen Sie sich an, was passiert.

Wenn Sie dann einen anderen Tab im Browser öffnen, zu einer Site surfen und dann den Tab schließen, auf dem sich die andere Seite befand, auf der der Browser leckte, wird der Browser Ihrer Meinung nach den Speicher freigeben? Nicht so beim IE. Auf meinem Computer verbraucht der Internet Explorer in kurzer Zeit (ca. 3-4 Tage) problemlos 1 GB Speicher, wenn ich Google Reader verwende. Einige Zeitungen sind noch schlimmer.

neslekkiM
quelle
10

Ich denke, in einer verwalteten Umgebung wäre ein Leck, wenn Sie einen unnötigen Verweis auf einen großen Speicherblock behalten würden.

Absolut. Wenn Sie die Methode .Dispose () nicht für Einwegobjekte verwenden, kann dies zu Memlecks führen. Der einfachste Weg, dies zu tun, ist ein using-Block, da er am Ende automatisch .Dispose () ausführt:

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

Wenn Sie eine Klasse erstellen, die nicht verwaltete Objekte verwendet, und IDisposable nicht korrekt implementieren, kann dies zu Speicherverlusten für die Benutzer Ihrer Klasse führen.

Seibar
quelle
9

Alle Speicherlecks werden durch Programmbeendigung behoben.

Es fehlt genügend Speicher, und das Betriebssystem kann entscheiden, das Problem in Ihrem Namen zu beheben.

Josh
quelle
8

Ich werde Bernard in .net zustimmen, was ein Mem-Leck wäre.

Sie können Ihre Anwendung profilieren, um die Speichernutzung zu sehen, und feststellen, dass sie ein Leck aufweist, wenn sie viel Speicher verwaltet, wenn dies nicht der Fall sein sollte.

In verwalteten Begriffen werde ich meinen Hals auf die Linie legen, um zu sagen, dass er verschwindet, sobald der Prozess beendet / entfernt wird.

Nicht verwalteter Code ist sein eigenes Biest, und wenn ein Leck darin vorhanden ist, folgt er einem Standardmem. Leckdefinition.

Klopfen
quelle
7

Beachten Sie auch, dass .NET zwei Heaps hat, von denen einer der Heap für große Objekte ist. Ich glaube, dass Objekte von ungefähr 85.000 oder mehr auf diesen Haufen gelegt werden. Dieser Heap hat andere Lebenszeitregeln als der reguläre Heap.

Wenn Sie große Speicherstrukturen (Wörterbücher oder Listen) erstellen, ist es ratsam, nach den genauen Regeln zu suchen.

Soweit der Speicher bei Prozessbeendigung wiederhergestellt wird, wird bei Beendigung von Win98 alles an das Betriebssystem zurückgegeben, es sei denn, Sie führen Win98 aus oder es entspricht. Die einzigen Ausnahmen sind Dinge, die prozessübergreifend geöffnet werden und bei einem anderen Prozess ist die Ressource noch offen.

COM-Objekte können schwierig sein. Wenn Sie immer das IDisposeMuster verwenden, sind Sie sicher. Aber ich bin auf einige Interop-Assemblys gestoßen, die implementiert werden IDispose. Der Schlüssel hier ist anzurufen, Marshal.ReleaseCOMObjectwenn Sie damit fertig sind. Die COM-Objekte verwenden weiterhin die Standard-COM-Referenzzählung.

Joel Lucsy
quelle
6

Ich fand .Net Memory Profiler eine sehr gute Hilfe beim Auffinden von Speicherlecks in .Net. Es ist nicht kostenlos wie der Microsoft CLR Profiler, aber meiner Meinung nach schneller und auf den Punkt gebracht. EIN

Lars Truijens
quelle