Müssen Sie Objekte entsorgen und auf null setzen, oder bereinigt der Garbage Collector sie, wenn sie den Gültigkeitsbereich verlassen?
310
Müssen Sie Objekte entsorgen und auf null setzen, oder bereinigt der Garbage Collector sie, wenn sie den Gültigkeitsbereich verlassen?
Dispose()
Methode einige Verweise auf null setzen ! Dies ist eine subtile Variation dieser Frage, aber wichtig, da das zu entsorgende Objekt nicht wissen kann, ob es "außerhalb des Gültigkeitsbereichs" liegt (AufrufDispose()
ist keine Garantie). Mehr hier: stackoverflow.com/questions/6757048/…Antworten:
Objekte werden bereinigt, wenn sie nicht mehr verwendet werden und wenn der Garbage Collector dies für richtig hält. Manchmal müssen Sie möglicherweise ein Objekt festlegen
null
, um den Gültigkeitsbereich zu verlassen (z. B. ein statisches Feld, dessen Wert Sie nicht mehr benötigen), aber insgesamt muss es normalerweise nicht festgelegt werdennull
.In Bezug auf die Entsorgung von Gegenständen stimme ich @Andre zu. Wenn das Objekt
IDisposable
ist es eine gute Idee, es zu entsorgen , wenn Sie nicht mehr benötigen, vor allem , wenn das Objekt nicht verwalteten Ressourcen verwendet. Wenn nicht verwaltete Ressourcen nicht entsorgt werden, kommt es zu Speicherverlusten .Mit der
using
Anweisung können Sie ein Objekt automatisch entsorgen, sobald Ihr Programm den Gültigkeitsbereich derusing
Anweisung verlässt .Welches ist funktional äquivalent zu:
quelle
if (obj != null) ((IDisposable)obj).Dispose();
IDisposable
. Wenn ein Objekt nicht entsorgt wird, führt dies im Allgemeinen nicht zu einem Speicherverlust bei einer gut gestalteten Klasse. Wenn Sie mit nicht verwalteten Ressourcen in C # arbeiten, sollten Sie einen Finalizer haben, der die nicht verwalteten Ressourcen weiterhin freigibt. Dies bedeutet, dass anstelle der Freigabe der Ressourcen, wenn dies durchgeführt werden soll, der Zeitpunkt verschoben wird, zu dem der Garbage Collector das verwaltete Objekt fertigstellt. Es kann jedoch noch viele andere Probleme verursachen (z. B. nicht freigegebene Sperren). Sie sollten eineIDisposable
obwohl entsorgen !Objekte verlassen in C # niemals den Gültigkeitsbereich wie in C ++. Sie werden vom Garbage Collector automatisch bearbeitet, wenn sie nicht mehr verwendet werden. Dies ist ein komplizierterer Ansatz als C ++, bei dem der Umfang einer Variablen vollständig deterministisch ist. Der CLR-Garbage Collector durchsucht aktiv alle erstellten Objekte und ermittelt, ob sie verwendet werden.
Ein Objekt kann in einer Funktion "außerhalb des Gültigkeitsbereichs" liegen. Wenn jedoch sein Wert zurückgegeben wird, prüft GC, ob die aufrufende Funktion den Rückgabewert beibehält oder nicht.
Das Festlegen von Objektreferenzen auf
null
ist nicht erforderlich, da die Speicherbereinigung funktioniert, indem ermittelt wird, auf welche Objekte von anderen Objekten verwiesen wird.In der Praxis muss man sich keine Sorgen um Zerstörung machen, es funktioniert einfach und es ist großartig :)
Dispose
muss für alle Objekte aufgerufen werden, die implementiert werden,IDisposable
wenn Sie mit ihnen fertig sind. Normalerweise würden Sie einenusing
Block mit folgenden Objekten verwenden:BEARBEITEN Auf variablen Bereich. Craig hat gefragt, ob der variable Bereich Auswirkungen auf die Objektlebensdauer hat. Um diesen Aspekt der CLR richtig zu erklären, muss ich einige Konzepte aus C ++ und C # erläutern.
Tatsächlicher variabler Umfang
In beiden Sprachen kann die Variable nur in demselben Bereich verwendet werden, in dem sie definiert wurde - Klasse, Funktion oder ein durch Klammern eingeschlossener Anweisungsblock. Der subtile Unterschied besteht jedoch darin, dass in C # Variablen in einem verschachtelten Block nicht neu definiert werden können.
In C ++ ist dies völlig legal:
In C # wird jedoch ein Compilerfehler angezeigt:
Dies ist sinnvoll, wenn Sie sich die generierte MSIL ansehen - alle von der Funktion verwendeten Variablen werden zu Beginn der Funktion definiert. Schauen Sie sich diese Funktion an:
Unten ist die generierte IL. Beachten Sie, dass iVal2, das im if-Block definiert ist, tatsächlich auf Funktionsebene definiert ist. Tatsächlich bedeutet dies, dass C # nur einen Bereich auf Klassen- und Funktionsebene hat, was die variable Lebensdauer betrifft.
C ++ - Bereich und Objektlebensdauer
Immer wenn eine auf dem Stapel zugewiesene C ++ - Variable den Gültigkeitsbereich verlässt, wird sie zerstört. Denken Sie daran, dass Sie in C ++ Objekte auf dem Stapel oder auf dem Heap erstellen können. Wenn Sie sie auf dem Stapel erstellen, werden sie vom Stapel gestapelt und zerstört, sobald die Ausführung den Bereich verlässt.
Wenn C ++ - Objekte auf dem Heap erstellt werden, müssen sie explizit zerstört werden, da es sich sonst um einen Speicherverlust handelt. Kein solches Problem mit Stapelvariablen.
C # Objektlebensdauer
In CLR werden Objekte (dh Referenztypen) immer auf dem verwalteten Heap erstellt. Dies wird durch die Objekterstellungssyntax weiter verstärkt. Betrachten Sie dieses Code-Snippet.
In C ++ würde dies eine Instanz
MyClass
auf dem Stapel erstellen und ihren Standardkonstruktor aufrufen. In C # würde ein Verweis auf eine Klasse erstelltMyClass
, der auf nichts verweist. Die einzige Möglichkeit, eine Instanz einer Klasse zu erstellen, ist die Verwendung desnew
Operators:In gewisser Weise ähneln C # -Objekte Objekten, die mithilfe der
new
Syntax in C ++ erstellt wurden. Sie werden auf dem Heap erstellt, werden jedoch im Gegensatz zu C ++ - Objekten von der Laufzeit verwaltet, sodass Sie sich nicht um deren Zerstörung kümmern müssen.Da sich die Objekte immer auf dem Heap befinden, wird die Tatsache, dass Objektreferenzen (dh Zeiger) außerhalb des Gültigkeitsbereichs liegen, umstritten. Es gibt mehr Faktoren, die bestimmen, ob ein Objekt gesammelt werden soll, als nur das Vorhandensein von Verweisen auf das Objekt.
C # Objektreferenzen
Jon Skeet verglich Objektreferenzen in Java mit Zeichenfolgen, die an der Sprechblase angebracht sind, die das Objekt ist. Die gleiche Analogie gilt für C # -Objektreferenzen. Sie zeigen einfach auf eine Position des Heaps, der das Objekt enthält. Das Setzen auf Null hat also keine unmittelbare Auswirkung auf die Objektlebensdauer. Der Ballon bleibt bestehen, bis der GC ihn "knallt".
Wenn Sie die Ballonanalogie fortsetzen, erscheint es logisch, dass der Ballon zerstört werden kann, wenn er keine Schnüre mehr hat. Genau so funktionieren referenzgezählte Objekte in nicht verwalteten Sprachen. Nur dass dieser Ansatz für Zirkelverweise nicht sehr gut funktioniert. Stellen Sie sich zwei Ballons vor, die durch eine Schnur miteinander verbunden sind, aber keiner der Ballons hat eine Schnur zu irgendetwas anderem. Nach einfachen Ref-Count-Regeln existieren beide weiterhin, obwohl die gesamte Ballongruppe "verwaist" ist.
.NET-Objekte ähneln Heliumballons unter einem Dach. Wenn sich das Dach öffnet (GC läuft), schweben die nicht verwendeten Ballons weg, obwohl möglicherweise Gruppen von Ballons miteinander verbunden sind.
.NET GC verwendet eine Kombination aus Generations-GC und Mark and Sweep. Bei einem generativen Ansatz wird die Laufzeit bevorzugt, um Objekte zu untersuchen, die zuletzt zugewiesen wurden, da sie mit größerer Wahrscheinlichkeit nicht verwendet werden. Beim Markieren und Durchlaufen wird die Laufzeit durchlaufen, um das gesamte Objektdiagramm zu durchlaufen und herauszufinden, ob Objektgruppen nicht verwendet werden. Dies behandelt das Problem der zirkulären Abhängigkeit angemessen.
Außerdem wird .NET GC auf einem anderen Thread (dem sogenannten Finalizer-Thread) ausgeführt, da es ziemlich viel zu tun hat und dies auf dem Haupt-Thread Ihr Programm unterbrechen würde.
quelle
Wie andere gesagt haben, möchten Sie auf jeden Fall anrufen,
Dispose
wenn die Klasse implementiertIDisposable
. Ich nehme dazu eine ziemlich starre Position ein. Man könnte behaupten , dass AufrufDispose
aufDataSet
, zum Beispiel, ist sinnlos , weil sie es zerlegt und sehen , dass es nicht sinnvoll , nichts getan. Aber ich denke, dieses Argument enthält viele Irrtümer.Lesen Sie dies für eine interessante Debatte von angesehenen Personen zu diesem Thema. Dann lesen Sie hier meine Argumentation , warum ich denke, dass Jeffery Richter im falschen Lager ist.
Nun zu der Frage, ob Sie einen Verweis auf festlegen sollen oder nicht
null
. Die Antwort ist nein. Lassen Sie mich meinen Standpunkt mit dem folgenden Code veranschaulichen.Wann kann das Objekt, auf das verwiesen
a
wird, Ihrer Meinung nach gesammelt werden? Wenn Sie nach dem Anruf gesagt haben,a = null
dann liegen Sie falsch. Wenn Sie nach Abschluss derMain
Methode gesagt haben, dass Sie auch falsch liegen. Die richtige Antwort ist, dass es irgendwann während des Anrufs bei zur Abholung berechtigt istDoSomething
. Das ist richtig. Es ist berechtigt, bevor die Referenz festgelegt wirdnull
und möglicherweise sogar bevor der AufrufDoSomething
abgeschlossen ist. Dies liegt daran, dass der JIT-Compiler erkennen kann, wenn Objektreferenzen nicht mehr dereferenziert werden, selbst wenn sie noch verwurzelt sind.quelle
a
sich ein privates Mitgliederfeld in einer Klasse befindet? Wenna
nicht auf null gesetzt ist, kann der GC nicht wissen, oba
er in einer Methode erneut verwendet wird, oder? Somita
wird nicht gesammelt, bis die gesamte enthaltende Klasse gesammelt ist. Nein?a
ein Klassenmitglied wäre und die Klasse, die enthält,a
noch verwurzelt und in Gebrauch wäre, würde es auch herumhängen. Dies ist ein Szenario, in dem die Einstellung vonnull
Vorteil sein kann.Dispose
wichtig ist: Es ist nicht möglich,Dispose
ein Objekt ohne einen verwurzelten Verweis darauf aufzurufen (oder eine andere nicht inlinierbare Methode). Wenn SieDispose
nach dem Aufrufen eines Objekts aufrufen, wird sichergestellt, dass während der gesamten Dauer der zuletzt ausgeführten Aktion weiterhin eine verwurzelte Referenz vorhanden ist. Das Verlassen aller Verweise auf ein Objekt ohne AufrufDispose
kann ironischerweise dazu führen, dass die Ressourcen des Objekts gelegentlich zu früh freigegeben werden .Sie müssen Objekte in C # niemals auf null setzen. Der Compiler und die Laufzeit kümmern sich darum, herauszufinden, wann sie nicht mehr im Umfang sind.
Ja, Sie sollten Objekte entsorgen, die IDisposable implementieren.
quelle
want
auf Null setzen, sobald Sie damit fertig sind, damit er frei zurückgefordert werden kann.Ich stimme der allgemeinen Antwort hier zu, dass Sie ja entsorgen sollten und nein, Sie sollten die Variable im Allgemeinen nicht auf null setzen ... aber ich wollte darauf hinweisen, dass es bei der Entsorgung NICHT in erster Linie um die Speicherverwaltung geht. Ja, es kann (und manchmal auch) bei der Speicherverwaltung helfen, aber sein Hauptzweck ist es, Ihnen eine deterministische Freigabe knapper Ressourcen zu ermöglichen.
Wenn Sie beispielsweise einen Hardware-Port (z. B. seriell), einen TCP / IP-Socket, eine Datei (im exklusiven Zugriffsmodus) oder sogar eine Datenbankverbindung öffnen, haben Sie jetzt verhindert, dass anderer Code diese Elemente verwendet, bis sie freigegeben werden. Dispose gibt diese Elemente im Allgemeinen frei (zusammen mit GDI und anderen "OS" -Handles usw., von denen 1000 verfügbar sind, aber insgesamt immer noch begrenzt sind). Wenn Sie dipose für das Eigentümerobjekt nicht aufrufen und diese Ressourcen explizit freigeben, versuchen Sie, dieselbe Ressource in Zukunft erneut zu öffnen (oder ein anderes Programm tut dies). Dieser Öffnungsversuch schlägt fehl, da für Ihr nicht verfügbares, nicht gesammeltes Objekt das Element noch geöffnet ist . Wenn der GC den Gegenstand sammelt (wenn das Entsorgungsmuster korrekt implementiert wurde), wird die Ressource natürlich freigegeben ... aber Sie wissen nicht, wann dies sein wird, also tun Sie es nicht. Ich weiß nicht, wann es sicher ist, diese Ressource erneut zu öffnen. Dies ist das Hauptproblem, das Dispose umgeht. Wenn Sie diese Handles freigeben, wird häufig auch Speicher freigegeben, und wenn Sie sie niemals freigeben, wird dieser Speicher möglicherweise nie freigegeben. Daher wird häufig über Speicherlecks oder Verzögerungen bei der Speicherbereinigung gesprochen.
Ich habe Beispiele aus der Praxis gesehen, die Probleme verursachen. Zum Beispiel habe ich ASP.Net-Webanwendungen gesehen, die möglicherweise keine Verbindung zur Datenbank herstellen können (wenn auch für kurze Zeit oder bis zum Neustart des Webserverprozesses), weil der Verbindungspool des SQL-Servers voll ist ... dh Es wurden so viele Verbindungen erstellt und nicht explizit in so kurzer Zeit freigegeben, dass keine neuen Verbindungen erstellt werden können und viele der Verbindungen im Pool, obwohl sie nicht aktiv sind, immer noch von nicht abgelegten und nicht gesammelten Objekten referenziert werden. nicht wiederverwendet werden. Wenn Sie die Datenbankverbindungen bei Bedarf ordnungsgemäß entsorgen, tritt dieses Problem nicht auf (zumindest nicht, es sei denn, Sie haben einen sehr hohen gleichzeitigen Zugriff).
quelle
Wenn das Objekt implementiert
IDisposable
ist, sollten Sie es entsorgen. Das Objekt hängt möglicherweise an nativen Ressourcen (Dateihandles, Betriebssystemobjekte), die ansonsten möglicherweise nicht sofort freigegeben werden. Dies kann zu Ressourcenmangel, Problemen beim Sperren von Dateien und anderen subtilen Fehlern führen, die andernfalls vermieden werden könnten.Siehe auch Implementieren einer Entsorgungsmethode in MSDN.
quelle
Dispose
die aufgerufen wird. Wenn Ihr Objekt an einer knappen Ressource festhält oder eine Ressource (z. B. eine Datei) sperrt, sollten Sie diese so schnell wie möglich freigeben. Das Warten auf den GC ist nicht optimal.might
anrufen, sondernwill
anrufen.Wenn sie die IDisposable-Schnittstelle implementieren, sollten Sie sie entsorgen. Der Müllsammler kümmert sich um den Rest.
BEARBEITEN: Verwenden Sie am besten den
using
Befehl, wenn Sie mit Einwegartikeln arbeiten:quelle
Wenn ein Objekt implementiert wird
IDisposable
, sollten Sie aufrufenDispose
(oderClose
in einigen Fällen wird Dispose für Sie aufgerufen).Normalerweise müssen Sie keine Objekte festlegen
null
, da der GC weiß, dass ein Objekt nicht mehr verwendet wird.Es gibt eine Ausnahme, wenn ich Objekte auf setze
null
. Wenn ich viele Objekte (aus der Datenbank) abrufe, an denen ich arbeiten muss, und sie in einer Sammlung (oder einem Array) speichere. Wenn die "Arbeit" erledigt ist, setze ich das Objekt aufnull
, weil der GC nicht weiß, dass ich mit der Arbeit fertig bin.Beispiel:
quelle
Normalerweise müssen Felder nicht auf null gesetzt werden. Ich würde jedoch immer empfehlen, nicht verwaltete Ressourcen zu entsorgen.
Aus Erfahrung würde ich Ihnen auch raten, Folgendes zu tun:
Ich bin auf einige sehr schwer zu findende Probleme gestoßen, die das direkte Ergebnis der Nichtbefolgung der obigen Ratschläge waren.
Ein guter Ort, um dies zu tun, ist Dispose (), aber früher ist normalerweise besser.
Wenn ein Verweis auf ein Objekt vorhanden ist, kann der Garbage Collector (GC) im Allgemeinen einige Generationen länger dauern, um festzustellen, dass ein Objekt nicht mehr verwendet wird. Währenddessen bleibt das Objekt im Speicher.
Dies ist möglicherweise kein Problem, bis Sie feststellen, dass Ihre App viel mehr Speicher belegt als erwartet. Schließen Sie in diesem Fall einen Speicherprofiler an, um festzustellen, welche Objekte nicht bereinigt werden. Das Festlegen von Feldern, die andere Objekte auf Null verweisen, und das Löschen von Sammlungen bei der Entsorgung können dem GC wirklich dabei helfen, herauszufinden, welche Objekte er aus dem Speicher entfernen kann. Der GC stellt den verwendeten Speicher schneller wieder her, wodurch Ihre App weniger speicherhungrig und schneller wird.
quelle
Rufen Sie immer dispose an. Das Risiko ist es nicht wert. Große verwaltete Unternehmensanwendungen sollten mit Respekt behandelt werden. Es können keine Annahmen getroffen werden, sonst wird es zurückkommen, um Sie zu beißen.
Hör nicht auf Leppie.
Viele Objekte implementieren IDisposable nicht, sodass Sie sich keine Sorgen machen müssen. Wenn sie wirklich außerhalb des Geltungsbereichs liegen, werden sie automatisch freigegeben. Außerdem bin ich nie auf die Situation gestoßen, in der ich etwas auf Null setzen musste.
Eine Sache, die passieren kann, ist, dass viele Objekte offen gehalten werden können. Dies kann die Speichernutzung Ihrer Anwendung erheblich erhöhen. Manchmal ist es schwierig herauszufinden, ob dies tatsächlich ein Speicherverlust ist oder ob Ihre Anwendung nur eine Menge Dinge tut.
Speicherprofil-Tools können bei solchen Dingen helfen, aber es kann schwierig sein.
Außerdem können Sie Ereignisse, die nicht benötigt werden, immer abbestellen. Seien Sie auch vorsichtig mit der WPF-Bindung und den Kontrollen. Keine übliche Situation, aber ich stieß auf eine Situation, in der ich ein WPF-Steuerelement hatte, das an ein zugrunde liegendes Objekt gebunden war. Das zugrunde liegende Objekt war groß und beanspruchte viel Speicher. Das WPF-Steuerelement wurde durch eine neue Instanz ersetzt, und die alte Instanz hing aus irgendeinem Grund immer noch herum. Dies verursachte einen großen Speicherverlust.
Im Nachhinein war der Code schlecht geschrieben, aber der Punkt ist, dass Sie sicherstellen möchten, dass Dinge, die nicht verwendet werden, außerhalb des Geltungsbereichs liegen. Es hat lange gedauert, diesen mit einem Speicherprofiler zu finden, da es schwierig ist zu wissen, welche Inhalte im Speicher gültig sind und welche nicht vorhanden sein sollten.
quelle
Ich muss auch antworten. Die JIT generiert Tabellen zusammen mit dem Code aus ihrer statischen Analyse der Variablennutzung. Diese Tabelleneinträge sind die "GC-Roots" im aktuellen Stapelrahmen. Mit fortschreitendem Befehlszeiger werden diese Tabelleneinträge ungültig und sind somit für die Speicherbereinigung bereit. Deshalb: Wenn es sich um eine Gültigkeitsbereichsvariable handelt, müssen Sie sie nicht auf null setzen - der GC sammelt das Objekt. Wenn es sich um ein Mitglied oder eine statische Variable handelt, müssen Sie sie auf null setzen
quelle