Best Practice zum Erzwingen der Speicherbereinigung in C #

118

Nach meiner Erfahrung scheinen die meisten Leute Ihnen zu sagen, dass es unklug ist, eine Speicherbereinigung zu erzwingen, aber in einigen Fällen, wenn Sie mit großen Objekten arbeiten, die nicht immer in der 0-Generation gesammelt werden, aber Speicher ein Problem darstellt, ist dies der Fall Ist es in Ordnung, das Sammeln zu erzwingen? Gibt es dafür eine bewährte Methode?

Echostorm
quelle

Antworten:

112

Die beste Vorgehensweise besteht darin, keine Speicherbereinigung zu erzwingen.

Laut MSDN:

"Es ist möglich, die Speicherbereinigung durch Aufrufen von Collect zu erzwingen. In den meisten Fällen sollte dies jedoch vermieden werden, da dies zu Leistungsproblemen führen kann."

Wenn Sie Ihren Code jedoch zuverlässig testen können, um zu bestätigen, dass der Aufruf von Collect () keine negativen Auswirkungen hat, fahren Sie fort ...

Stellen Sie einfach sicher, dass Objekte bereinigt werden, wenn Sie sie nicht mehr benötigen. Wenn Sie benutzerdefinierte Objekte haben, verwenden Sie die "using-Anweisung" und die IDisposable-Schnittstelle.

Dieser Link enthält einige gute praktische Ratschläge zum Freigeben von Speicher / Speicherbereinigung usw.:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

Mark Ingram
quelle
3
Sie können auch verschiedene LatencyModes msdn.microsoft.com/en-us/library/bb384202.aspx
David d C e Freitas
4
Wenn Ihre Objekte auf nicht verwalteten Speicher verweisen, können Sie den Garbage Collector über die GC.AddMemoryPressure-API ( msdn.microsoft.com/en-us/library/… ) informieren . Dadurch erhält der Garbage Collector mehr Informationen über Ihr System, ohne die Erfassungsalgorithmen zu beeinträchtigen.
Govert
+1: * Sehen Sie sich die Verwendung der "using-Anweisung" und der IDisposable-Schnittstelle an. * Ich würde nicht einmal das Erzwingen in Betracht ziehen, außer als letztes Mittel - guter Rat (gelesen als "Haftungsausschluss"). Ich zwinge jedoch die Sammlung in einem Komponententest, um den Verlust einer aktiven Referenz in einer Back-End-Operation zu simulieren - und letztendlich eine zu werfen TargetOfInvocationNullException.
IAbstract
33

Betrachten Sie es so - ist es effizienter, den Küchenmüll wegzuwerfen, wenn der Mülleimer 10% beträgt, oder ihn vor dem Herausnehmen auffüllen zu lassen?

Wenn Sie es nicht auffüllen lassen, verschwenden Sie Ihre Zeit damit, von und zur Mülltonne draußen zu gehen. Dies ist analog zu dem, was passiert, wenn der GC-Thread ausgeführt wird. Alle verwalteten Threads werden angehalten, während er ausgeführt wird. Und wenn ich mich nicht irre, kann der GC-Thread von mehreren AppDomains gemeinsam genutzt werden, sodass die Speicherbereinigung alle betrifft.

Natürlich kann es vorkommen, dass Sie dem Müll bald nichts mehr hinzufügen - sagen wir, wenn Sie Urlaub machen. Dann wäre es eine gute Idee, den Müll wegzuwerfen, bevor Sie ausgehen.

Dies könnte ein Mal sein, dass das Erzwingen eines GC hilfreich sein kann. Wenn Ihr Programm inaktiv ist, wird der verwendete Speicher nicht durch Speicherbereinigung erfasst, da keine Zuordnungen vorhanden sind.

Maxam
quelle
5
Wenn Sie ein Baby hätten, das sterben würde, wenn Sie es länger als eine Minute verlassen würden, und Sie immer nur eine Minute Zeit hätten, um mit dem Müll umzugehen, dann würden Sie jedes Mal ein wenig tun wollen, anstatt alles auf einmal. Leider ist die GC :: Collect () -Methode nicht schneller, je öfter Sie sie aufrufen. Wenn Sie für eine Echtzeit-Engine nicht einfach den Entsorgungsmechanismus verwenden und den GC Ihre Daten bündeln lassen können, sollten Sie kein verwaltetes System verwenden - gemäß meiner Antwort (wahrscheinlich unter diesem, lol).
Jin
1
In meinem Fall führe ich einen A * -Algorithmus (kürzester Pfad) WIEDERHOLT aus, um die Leistung zu optimieren ... der in der Produktion nur einmal ausgeführt wird (auf jeder "Karte"). Daher möchte ich, dass der GC vor jeder Iteration außerhalb meines "Blocks für die Leistungsmessung" durchgeführt wird, da ich der Meinung bin, dass dies die Situation in der Produktion genauer modelliert, in der der GC nach dem Navigieren in jeder "Karte" erzwungen werden könnte / sollte.
Corlettk
Das Aufrufen von GC vor dem gemessenen Block modelliert die Situation in der Produktion nicht, da die GC in der Produktion in unvorhersehbaren Zeiten durchgeführt wird. Um dies zu vermeiden, sollten Sie eine lange Messung durchführen, die mehrere GC-Ausführungen umfasst, und die Peaks während der GC in Ihre Analyse und Statistik einbeziehen.
Ran
32

In den meisten Fällen wird empfohlen, keine Speicherbereinigung zu erzwingen. (Jedes System, an dem ich gearbeitet habe, hatte die Speicherbereinigung erzwungen, Probleme unterstrichen, die bei einer Lösung die Notwendigkeit der Speicherbereinigung beseitigt hätten, und das System erheblich beschleunigt.)

Es gibt ein paar Fälle , wenn Sie mehr über die Speichernutzung wissen dann der Garbage Collector tut. Es ist unwahrscheinlich, dass dies in einer Mehrbenutzeranwendung oder einem Dienst der Fall ist, der auf mehr als eine Anfrage gleichzeitig reagiert.

Bei einigen Stapelverarbeitungen wissen Sie jedoch mehr als der GC. Betrachten Sie beispielsweise eine Anwendung, die.

  • Erhält eine Liste der Dateinamen in der Befehlszeile
  • Verarbeitet eine einzelne Datei und schreibt das Ergebnis in eine Ergebnisdatei.
  • Erstellt während der Verarbeitung der Datei viele miteinander verknüpfte Objekte, die erst nach Abschluss der Verarbeitung der Datei erfasst werden können (z. B. ein Analysebaum).
  • Hält nicht viel Status zwischen den verarbeiteten Dateien .

Möglicherweise können Sie (nach sorgfältiger Prüfung) prüfen, ob Sie eine vollständige Speicherbereinigung erzwingen sollten, nachdem Sie jede Datei verarbeitet haben.

Ein anderer Fall ist ein Dienst, der alle paar Minuten aufwacht, um einige Elemente zu verarbeiten, und keinen Zustand beibehält, während er schläft . Dann kann es sich lohnen, kurz vor dem Schlafengehen eine vollständige Sammlung zu erzwingen .

Ich würde nur in Betracht ziehen, eine Sammlung zu erzwingen, wenn ich weiß, dass in letzter Zeit viele Objekte erstellt wurden und derzeit nur auf sehr wenige Objekte verwiesen wird.

Ich hätte lieber eine Garbage Collection-API, wenn ich ihr Hinweise zu solchen Dingen geben könnte, ohne selbst einen GC erzwingen zu müssen.

Siehe auch " Rico Marianis Performance-Leckerbissen "

Ian Ringrose
quelle
2
Analogie: Playschool (System) hält Buntstifte (Ressourcen). Abhängig von der Anzahl der Kinder (Aufgaben) und der Farbknappheit entscheidet der Lehrer (.Net), wie er die Kinder verteilt und teilt. Wenn eine seltene Farbe angefordert wird, kann der Lehrer aus dem Pool zuweisen oder nach einer suchen, die nicht verwendet wird. Es liegt im Ermessen des Lehrers, nicht verwendete Buntstifte regelmäßig zu sammeln (Müllabfuhr), um Ordnung zu schaffen (Optimierung der Ressourcennutzung). Im Allgemeinen kann ein Elternteil (Programmierer) nicht die beste Richtlinie für das Aufräumen von Buntstiften im Klassenzimmer festlegen. Es ist unwahrscheinlich, dass das geplante Nickerchen eines Kindes ein guter Moment ist, um die Färbung der anderen Kinder zu beeinträchtigen.
AlanK
1
@AlanK, ich mag das, denn wenn die Kinder für einen Tag nach Hause gehen, ist es eine sehr gute Zeit für den Lehrerhelfer, gut aufzuräumen, ohne dass die Kinder im Weg stehen. (Neuere Systeme, an denen ich gearbeitet habe, haben gerade den Serviceprozess zu solchen Zeiten neu gestartet, um GC zu erzwingen.)
Ian Ringrose
21

Ich denke, das Beispiel von Rico Mariani war gut: Es kann angebracht sein, einen GC auszulösen, wenn sich der Status der Anwendung erheblich ändert. In einem Dokumenteditor kann es beispielsweise in Ordnung sein, einen GC auszulösen, wenn ein Dokument geschlossen wird.

Denis Phillips
quelle
2
Oder kurz vor dem Öffnen eines großen zusammenhängenden Objekts, das eine Fehlerhistorie aufweist und keine effiziente Lösung zur Erhöhung seiner Granularität bietet.
Crokusek
17

Es gibt nur wenige allgemeine Richtlinien für die Programmierung, die absolut sind. In der Hälfte der Fälle, in denen jemand sagt, dass Sie es falsch machen, spricht er nur eine bestimmte Menge an Dogmen aus. In C war es früher Angst vor Dingen wie selbstmodifizierendem Code oder Threads, in GC-Sprachen erzwingt es den GC oder verhindert alternativ, dass der GC ausgeführt wird.

Wie bei den meisten Richtlinien und guten Faustregeln (und guten Entwurfspraktiken) gibt es seltene Fälle, in denen es sinnvoll ist, die etablierte Norm zu umgehen. Sie müssen sehr sicher sein, dass Sie den Fall verstehen, dass Ihr Fall wirklich die Aufhebung der gängigen Praxis erfordert und dass Sie die Risiken und Nebenwirkungen verstehen, die Sie verursachen können. Aber es gibt solche Fälle.

Programmierprobleme sind sehr unterschiedlich und erfordern einen flexiblen Ansatz. Ich habe Fälle gesehen, in denen es sinnvoll ist, GC in von Müll gesammelten Sprachen zu blockieren, und Orte, an denen es sinnvoll ist, sie auszulösen, anstatt darauf zu warten, dass sie auf natürliche Weise auftritt. In 95% der Fälle wäre beides ein Wegweiser dafür, dass das Problem nicht richtig angegangen wurde. Aber 1 Mal in 20 gibt es wahrscheinlich einen gültigen Fall dafür.


quelle
12

Ich habe gelernt, nicht zu versuchen, die Garbage Collection zu überlisten. Trotzdem bleibe ich bei der Verwendung von usingSchlüsselwörtern, wenn es um nicht verwaltete Ressourcen wie Datei-E / A oder Datenbankverbindungen geht.

Kon
quelle
27
Compiler? Was hat der Compiler mit GC zu tun? :)
KristoferA
1
Nichts, es kompiliert und optimiert nur Code. Und das hat definitiv nichts mit der CLR zu tun ... oder sogar mit .NET.
Kon
1
Objekte, die viel Speicher belegen, wie sehr große Bilder, werden möglicherweise nicht mit Müll gesammelt, es sei denn, Sie sammeln sie explizit mit Müll. Ich denke, dies (große Objekte) war mehr oder weniger das Problem des OP.
Code4life
1
Wenn Sie es in eine Verwendung einschließen, wird sichergestellt, dass es für die GC geplant ist, sobald der Anwendungsbereich nicht mehr verwendet wird. Wenn der Computer nicht explodiert, wird dieser Speicher höchstwahrscheinlich bereinigt.
Kon
9

Ich bin mir nicht sicher, ob es eine bewährte Methode ist, aber wenn ich mit großen Mengen von Bildern in einer Schleife arbeite (dh viele Grafik- / Bild- / Bitmap-Objekte erstellen und entsorgen), lasse ich das GC.Collect regelmäßig.

Ich glaube, ich habe irgendwo gelesen, dass der GC nur ausgeführt wird, wenn das Programm (meistens) im Leerlauf ist und nicht mitten in einer intensiven Schleife, so dass dies wie ein Bereich aussehen könnte, in dem manueller GC sinnvoll sein könnte.

Michael Stum
quelle
Bist du sicher, dass du das brauchst? Die GC wird sammeln , wenn es Speicher benötigt, auch wenn der Code nicht im Leerlauf ist.
Konrad Rudolph
Ich bin mir nicht sicher, wie es jetzt in .net 3.5 SP1 ist, aber zuvor (1.1 und ich glaube, ich habe gegen 2.0 getestet) hat es einen Unterschied in der Speichernutzung gemacht. Der GC sammelt natürlich immer bei Bedarf, aber Sie könnten immer noch 100 Megabyte RAM verschwenden, wenn Sie nur 20 benötigen. Würde allerdings ein paar weitere Tests benötigen
Michael Stum
2
GC wird bei der Speicherzuweisung ausgelöst, wenn die Generation 0 einen bestimmten Schwellenwert erreicht (z. B. 1 MB), nicht wenn "etwas inaktiv ist". Andernfalls könnte OutOfMemoryException in einer Schleife auftreten, indem Sie Objekte einfach zuweisen und sofort verwerfen.
liggett78
5
Die 100 Megabyte RAM werden nicht verschwendet, wenn keine anderen Prozesse sie benötigen. Es gibt Ihnen einen schönen Leistungsschub :-P
Orion Edwards
9

Ein Fall, auf den ich kürzlich gestoßen bin und für den manuelle Aufrufe erforderlich waren, GC.Collect()war die Arbeit mit großen C ++ - Objekten, die in winzige verwaltete C ++ - Objekte eingeschlossen waren, auf die wiederum über C # zugegriffen wurde.

Der Garbage Collector wurde nie aufgerufen, da die Menge des verwendeten verwalteten Speichers vernachlässigbar war, aber die Menge des verwendeten nicht verwalteten Speichers war riesig. Das manuelle Aufrufen Dispose()der Objekte würde erfordern, dass ich selbst verfolge, wann Objekte nicht mehr benötigt werden, während das Aufrufen GC.Collect()alle Objekte bereinigt, auf die nicht mehr verwiesen wird .....

Morten
quelle
6
Eine bessere Möglichkeit, dies zu lösen, besteht darin, GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)den Konstruktor und später GC.RemoveMemoryPressure(addedSize)den Finalizer aufzurufen . Auf diese Weise wird der Garbage Collector automatisch ausgeführt, wobei die Größe der nicht verwalteten Strukturen berücksichtigt wird, die gesammelt werden könnten. stackoverflow.com/questions/1149181/…
HugoRune
Und eine noch bessere Möglichkeit, das Problem zu lösen, besteht darin, Dispose () aufzurufen, was Sie sowieso tun sollten.
Fabspro
2
Am besten verwenden Sie die Using-Struktur. Versuchen Sie / Schließlich. Entsorgen ist ein Ärger
TamusJRoyce
7

Ich denke, Sie haben bereits die Best Practice aufgelistet, und das ist NICHT zu verwenden, es sei denn, WIRKLICH notwendig. Ich würde dringend empfehlen, Ihren Code genauer zu betrachten und bei Bedarf möglicherweise Profiling-Tools zu verwenden, um diese Fragen zuerst zu beantworten.

  1. Haben Sie etwas in Ihrem Code, das Elemente in einem größeren Umfang als erforderlich deklariert?
  2. Ist die Speichernutzung wirklich zu hoch?
  3. Vergleichen Sie die Leistung vor und nach der Verwendung von GC.Collect (), um festzustellen, ob dies wirklich hilfreich ist.
Mitchel Sellers
quelle
5

Angenommen, Ihr Programm weist keinen Speicherverlust auf, Objekte sammeln sich an und können in Gen 0 nicht GC-bearbeitet werden, weil: 1) Sie für lange Zeit referenziert werden, also gehen Sie zu Gen1 & Gen2; 2) Es handelt sich um große Objekte (> 80 KB). Gehen Sie also zu LOH (Large Object Heap). Und LOH komprimiert nicht wie in Gen0, Gen1 und Gen2.

Überprüfen Sie den Leistungsindikator von ".NET Memory". Sie können sehen, dass das 1) Problem wirklich kein Problem ist. Im Allgemeinen löst jede 10 Gen0 GC 1 Gen1 GC aus, und jede 10 Gen1 GC löst 1 Gen2 GC aus. Theoretisch können GC1 und GC2 niemals GC-bearbeitet werden, wenn kein Druck auf GC0 ausgeübt wird (wenn die Programmspeicherauslastung wirklich verdrahtet ist). Das passiert mir nie.

Bei Problem 2) können Sie den Leistungsindikator ".NET Memory" überprüfen, um festzustellen, ob LOH aufgebläht ist. Wenn es sich wirklich um ein Problem handelt, können Sie möglicherweise einen Pool mit großen Objekten erstellen, wie in diesem Blog unter http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx vorgeschlagen .

Morgan Cheng
quelle
4

Große Objekte werden auf LOH (Large Object Heap) zugewiesen, nicht auf Gen 0. Wenn Sie sagen, dass sie mit Gen 0 nicht durch Müll gesammelt werden, haben Sie Recht. Ich glaube, sie werden nur gesammelt, wenn der gesamte GC-Zyklus (Generationen 0, 1 und 2) stattfindet.

Abgesehen davon glaube ich, dass GC auf der anderen Seite den Speicher aggressiver anpasst und sammelt, wenn Sie mit großen Objekten arbeiten und der Speicherdruck steigt.

Es ist schwer zu sagen, ob und unter welchen Umständen gesammelt werden soll oder nicht. Ich habe GC.Collect () ausgeführt, nachdem ich Dialogfenster / Formulare mit zahlreichen Steuerelementen usw. entsorgt hatte (weil das Formular und seine Steuerelemente zu dem Zeitpunkt in Gen 2 landen, weil viele Instanzen von Geschäftsobjekten erstellt / viele Daten geladen wurden - nein große Objekte offensichtlich), bemerkte aber auf lange Sicht keine positiven oder negativen Auswirkungen.

liggett78
quelle
4

Ich möchte Folgendes hinzufügen: Das Aufrufen von GC.Collect () (+ WaitForPendingFinalizers ()) ist ein Teil der Geschichte. Wie von anderen zu Recht erwähnt, ist GC.COllect () eine nicht deterministische Sammlung und liegt im Ermessen des GC selbst (CLR). Selbst wenn Sie WaitForPendingFinalizers einen Aufruf hinzufügen, ist dieser möglicherweise nicht deterministisch. Nehmen Sie den Code von diesem msdn- Link und führen Sie den Code mit der Objektschleifeniteration als 1 oder 2 aus. Sie werden feststellen, was nicht deterministisch bedeutet (setzen Sie einen Bruchpunkt im Destruktor des Objekts). Genau genommen wird der Destruktor nicht aufgerufen, wenn nur 1 (oder 2) verbleibende Objekte von Wait .. () vorhanden waren. [Zitieranforderung]

Wenn Ihr Code nicht verwaltete Ressourcen enthält (z. B. externe Dateihandles), müssen Sie Destruktoren (oder Finalizer) implementieren.

Hier ist ein interessantes Beispiel:

Hinweis : Wenn Sie das obige Beispiel bereits von MSDN aus ausprobiert haben, wird der folgende Code die Luft reinigen.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Ich schlage vor, zuerst zu analysieren, wie die Ausgabe aussehen könnte, dann auszuführen und dann den folgenden Grund zu lesen:

{Der Destruktor wird nur implizit aufgerufen, wenn das Programm endet. } Um das Objekt deterministisch zu bereinigen, muss IDisposable implementiert und Dispose () explizit aufgerufen werden. Das ist die Essenz! :) :)

Vaibhav
quelle
2

Wenn Sie GC Collect explizit auslösen, wird die Leistung Ihres Programms möglicherweise NICHT verbessert. Es ist durchaus möglich, es noch schlimmer zu machen.

Der .NET GC ist gut konzipiert und anpassungsfähig abgestimmt, was bedeutet, dass er den GC0 / 1/2-Schwellenwert entsprechend der "Gewohnheit" Ihrer Programmspeicherauslastung anpassen kann. So wird es nach einiger Zeit an Ihr Programm angepasst. Sobald Sie GC.Collect explizit aufrufen, werden die Schwellenwerte zurückgesetzt! Und das .NET muss Zeit aufwenden, um sich wieder an die "Gewohnheit" Ihres Programms anzupassen.

Mein Vorschlag ist immer, .NET GC zu vertrauen. Wenn Speicherprobleme auftreten, überprüfen Sie den Leistungsindikator ".NET Memory" und diagnostizieren Sie meinen eigenen Code.

Morgan Cheng
quelle
6
Ich denke, es ist besser, wenn Sie diese Antwort mit Ihrer vorherigen Antwort zusammenführen.
Salamander2007
1

Ich bin mir nicht sicher, ob es eine bewährte Methode ist ...

Vorschlag: Implementieren Sie dies oder etwas anderes nicht, wenn Sie sich nicht sicher sind. Bewerten Sie erneut, wenn Fakten bekannt sind, und führen Sie dann vor / nach Leistungstests durch, um dies zu überprüfen.

user957965
quelle
0

Wenn Sie Ihren Code jedoch zuverlässig testen können, um zu bestätigen, dass der Aufruf von Collect () keine negativen Auswirkungen hat, fahren Sie fort ...

IMHO, das ist ähnlich wie zu sagen: "Wenn Sie beweisen können, dass Ihr Programm in Zukunft keine Fehler mehr haben wird, dann fahren Sie fort ..."

In aller Ernsthaftigkeit ist das Erzwingen des GC zum Debuggen / Testen nützlich. Wenn Sie das Gefühl haben, dass Sie dies zu einem anderen Zeitpunkt tun müssen, irren Sie sich entweder oder Ihr Programm wurde falsch erstellt. In beiden Fällen erzwingt die Lösung nicht den GC ...

Orion Edwards
quelle
"Dann irren Sie sich entweder oder Ihr Programm wurde falsch erstellt. In beiden Fällen erzwingt die Lösung nicht den GC ..." Absolute sind fast immer nicht wahr. Es gibt einige außergewöhnliche Umstände, die Sinn machen.
rollt