C # .NET Garbage Collection funktioniert nicht?

7

Ich arbeite an einer relativ großen Lösung in Visual Studio 2010. Es gibt verschiedene Projekte, von denen eines ein XNA Game-Projekt ist und ein anderes ein ASP.NET MVC 2-Projekt.

Bei beiden Projekten stehe ich vor dem gleichen Problem: Nach dem Start im Debug-Modus steigt die Speichernutzung weiter an. Sie beginnen bei 40 bzw. 100 MB Speicherverbrauch, aber beide steigen relativ schnell auf 1,5 GB (10 bzw. 30 Minuten). Danach würde es manchmal bis kurz vor der ursprünglichen Verwendung zurückfallen, und zu anderen Zeiten würde es einfach OutOfMemoryExceptions werfen.

Natürlich deutet dies auf schwerwiegende Speicherlecks hin, also habe ich versucht, das Problem zu erkennen. Nachdem ich erfolglos nach Lecks gesucht habe, habe ich versucht, GC.Collect() regelmäßig aufzurufen (ungefähr einmal pro 10 Sekunden). Nach Einführung dieses "Hacks" blieb die Speicherbelegung 24 Stunden lang bei 45 bzw. 120 MB (bis ich mit dem Testen aufhörte).

Die Garbage-Collection von .NET soll "sehr gut" sein, aber ich kann nicht umhin zu vermuten, dass sie einfach nicht ihren Zweck erfüllt. Ich habe CLR Profiler in einem Versuch verwendet, das Problem zu beheben, und es zeigte, dass das XNA-Projekt eine Menge Bytearrays gespeichert hatte, die ich tatsächlich verwendete, aber die Verweise sollten bereits gelöscht und daher vom Müll gesammelt werden Sammler.

Auch wenn ich GC.Collect() regelmäßig anrufe, scheinen die Speicherprobleme verschwunden zu sein. Weiß jemand, was diese hohe Speichernutzung verursachen könnte? Ist es möglicherweise mit dem Ausführen im Debug-Modus verbunden?

    
Jacotb 16.06.2011, 15:52
quelle

6 Antworten

11
  

Nach erfolgloser Suche nach Lecks

Versuchen Sie es härter =)

Speicherlecks in einer verwalteten Sprache können schwierig zu finden sein. Ich habe gute Erfahrungen mit dem Redgate ANTS Memory Profiler gemacht. Es ist nicht kostenlos, aber Sie erhalten eine 14-tägige Testversion mit vollem Funktionsumfang. Es hat eine nette Benutzeroberfläche und zeigt Ihnen, wo Sie Speicher zugewiesen ist und warum diese Objekte im Speicher gehalten werden.

Wie Alex sagt, sind Ereignishandler eine sehr häufige Quelle für Speicherlecks in einer .NET-App. Bedenken Sie Folgendes:

%Vor%

Ich habe eine statische Klasse verwendet, um das Problem so offensichtlich wie möglich zu machen. Nehmen wir an, dass während der Lebensdauer Ihrer Anwendung viele Foo -Objekte erstellt werden. Jedes Foo abonniert das Ereignis SomeEvent der statischen Klasse.

Die Foo -Objekte können zu einem bestimmten Zeitpunkt aus dem Gültigkeitsbereich fallen, aber die statische Klasse behält über den Event-Handler-Delegaten einen Verweis auf jeden einzelnen. Somit werden sie auf unbestimmte Zeit am Leben erhalten. In diesem Fall muss der Event-Handler einfach "abgehakt" werden.

  

... das XNA-Projekt schien eine Menge Byte-Arrays gespeichert zu haben, die ich tatsächlich verwendete ...

Sie könnten in der LOH in Fragmentierung geraten. Wenn Sie große Objekte sehr häufig zuweisen, können sie das Problem verursachen. Die Gesamtgröße dieser Objekte ist möglicherweise viel kleiner als der gesamte der Laufzeit zugewiesene Speicher. Aufgrund der Fragmentierung wird Ihrer Anwendung jedoch viel ungenutzter Speicher zugewiesen.

Der Profiler, mit dem ich oben verlinkt bin, wird Ihnen sagen, ob das ein Problem ist. Wenn dies der Fall ist, können Sie es wahrscheinlich irgendwo zu einem Objektleck zurückverfolgen. Ich habe gerade ein Problem in meiner App behoben, das das gleiche Verhalten zeigt, und es war daran zu denken, dass MemoryStream seine interne byte[] nicht freigegeben hat, obwohl sie Dispose() aufgerufen hat. Das Wrapping des Streams in einem Dummy-Stream und das Auflösen des Streams beseitigten das Problem.

Stellen Sie unter Angabe des Offensichtlichen auch sicher, dass Dispose() Ihrer Objekte IDisposable implementiert. Es kann natürliche Ressourcen geben, die herumliegen. Auch hier wird ein guter Profiler das fangen.

Mein Vorschlag; Es ist nicht der GC, das Problem liegt in Ihrer App. Verwenden Sie einen Profiler, bringen Sie Ihre App in einen hohen Speicherverbrauch, erstellen Sie einen Speicher-Snapshot und beginnen Sie mit der Analyse.

    
Ed S. 16.06.2011, 16:14
quelle
5

In erster Linie funktioniert der GC und funktioniert gut. Es gibt keinen Fehler darin, den Sie gerade entdeckt haben.

Nun, da wir das aus dem Weg geschafft haben, ein paar Gedanken:

  1. Verwenden Sie zu viele Threads?
  2. Denken Sie daran, dass GC nicht deterministisch ist; es wird ausgeführt, wenn es denkt, dass es ausgeführt werden muss (selbst wenn Sie GC.Collect() aufrufen.
  3. Sind Sie sicher, dass all Ihre Referenzen nicht mehr in den Geltungsbereich fallen?
  4. Was laden Sie überhaupt in den Speicher? Große Bilder? Große Textdateien?

Ihr Profiler sollte Ihnen sagen, was so viel Speicher verbraucht. Fangen Sie an, die größten Übeltäter so oft wie möglich zu schneiden.

Auch das Aufrufen von GC.Collect () alle X Sekunden ist eine schlechte Idee und wird Ihr wahres Problem wahrscheinlich nicht lösen.

    
Esteban Araya 16.06.2011 15:57
quelle
4

Das Analysieren von Speicherproblemen in .NET ist keine einfache Aufgabe und Sie sollten auf jeden Fall mehrere gute Artikel lesen und verschiedene Tools ausprobieren, um das Ergebnis zu erzielen. Ich habe den folgenden Artikel nach den Untersuchungen: Ссылка Du kannst es auch versuchen Lesen Sie einige Artikel von Jeffrey Richter, wie diesen: Ссылка

Aus meiner Erfahrung gibt es zwei häufigste Gründe für ein Out-of-Memory-Problem:

  1. Event-Handler - sie können das Objekt auch dann halten, wenn keine anderen Objekte darauf verweisen. Im Idealfall müssen Sie Event-Handler abmelden, um das Objekt automatisch zu zerstören.
  2. Der Finalizer-Thread wird im STA-Modus von einem anderen Thread blockiert. Wenn beispielsweise der STA-Thread viel Arbeit verrichtet, werden die anderen Threads gestoppt und die Objekte, die sich in der Finalisierungswarteschlange befinden, können nicht gelöscht werden.
Alex Netkachov 16.06.2011 16:04
quelle
3

Bearbeiten: Link zur Fragmentierung von großen Objekthaufen hinzugefügt.

Bearbeiten: Da es so aussieht, als ob es ein Problem beim Zuweisen und Wegwerfen der Texturen ist, können Sie Texture2D.SetData verwenden, um das große Byte [] s wiederzuverwenden?

Zunächst müssen Sie herausfinden, ob es sich um verwalteten oder nicht verwalteten Speicher handelt, der undicht ist.

  1. Verwenden Sie perfmon, um zu sehen, was mit Ihrem Prozess passiert. '.net memory # Bytes in allen Heaps' und Process\Private Bytes . Vergleichen Sie die Zahlen und der Speicher steigt. Wenn der Anstieg von privaten Bytes den Anstieg des Heap-Speichers übersteigt, ist das Wachstum des nicht verwalteten Speichers groß.

  2. Nicht verwaltetes Speicherwachstum würde auf Objekte zeigen, die nicht entsorgt werden (aber schließlich gesammelt werden, wenn ihr Finalizer ausgeführt wird).

  3. Wenn das Speicherwachstum verwaltet wird, müssen wir sehen, welche Generation / LOH (es gibt auch Leistungsindikatoren für jede Generation von Heap-Bytes).

  4. Wenn es sich um Large Object Heap-Bytes handelt, sollten Sie die Verwendung und das Wegwerfen von Large-Byte-Arrays überdenken. Vielleicht können die Byte-Arrays wiederverwendet werden anstatt verworfen zu werden. Ziehen Sie auch die Zuweisung von großen Byte-Arrays mit Potenzen von 2 in Betracht. Auf diese Weise hinterlassen Sie bei der Anordnung ein großes "Loch" im großen Objekt-Heap, das von einem anderen Objekt der gleichen Größe ausgefüllt werden kann.

  5. Ein letztes Anliegen ist die Erinnerung, aber ich habe keinen Rat für Sie, weil ich es nie durcheinander gebracht habe.

agent-j 16.06.2011 16:25
quelle
1

Ich würde auch hinzufügen, dass, wenn Sie einen Dateizugriff machen, stellen Sie sicher, dass Sie alle Leser oder Autoren schließen und / oder entsorgen. Sie sollten eine übereinstimmende 1-1 zwischen dem Öffnen einer Datei und dem Schließen haben.

Auch verwende ich normalerweise die using-Klausel für Ressourcen, wie eine SQL-Verbindung:

%Vor%

Implementieren Sie IDisposable für beliebige Objekte und möglicherweise etwas Brauchbares, das Probleme verursacht? Ich würde alle IDisposable Codes überprüfen.

    
Jonathan Nixon 16.06.2011 16:12
quelle
0

Der GC berücksichtigt den nicht verwalteten Heap nicht. Wenn Sie viele Objekte erstellen, die in C # nur Wrapper in größeren nicht verwalteten Speicher sind, dann wird Ihr Speicher verschlungen, aber der GC kann keine rationalen Entscheidungen treffen, da er nur den verwalteten Heap erkennt.

Sie befinden sich in einer Situation, in der der GC-Kollektor nicht denkt, dass Sie wenig Speicher haben, da die meisten Dinge auf Ihrem gen 1-Heap 8-Byte-Referenzen sind, wo sie tatsächlich wie Eisberge auf hoher See sind. Der größte Teil des Speichers ist darunter!

Sie können diese GC-Aufrufe verwenden:

%Vor%

Diese Methoden ermöglichen es dem Garbage Collector, den nicht verwalteten Speicher zu sehen (wenn Sie ihm die richtigen Zahlen geben)

    
UltimateDRT 20.10.2016 08:23
quelle

Tags und Links