Sie können wirklich keine pauschalen Aussagen über die angemessene Verwendung aller GC-Implementierungen machen. Sie variieren stark. Also spreche ich mit dem .NET, auf das Sie sich ursprünglich bezogen haben.
Sie müssen das Verhalten des GC ziemlich genau kennen, um dies mit einer Logik oder einem Grund zu tun.
Der einzige Rat, den ich zum Sammeln geben kann, ist: Tu es niemals.
Wenn Sie die komplizierten Details des GC wirklich kennen, brauchen Sie meinen Rat nicht, es spielt also keine Rolle. Wenn Sie es noch nicht zu 100% sicher wissen, kann es hilfreich sein, online nachzuschauen und eine Antwort wie die folgende zu finden: Sie sollten GC.Collect nicht anrufen oder alternativ: Sie sollten sich mit den Einzelheiten der Funktionsweise des GC vertraut machen innen und außen, und nur dann werden Sie wissen die Antwort .
Es gibt einen sicheren Ort, an dem es sinnvoll ist, GC.Collect zu verwenden :
GC.Collect ist eine API, die Sie zum Erstellen von Zeitprofilen für bestimmte Dinge verwenden können. Sie können einen Algorithmus profilieren, sammeln und einen anderen Algorithmus unmittelbar danach profilieren, wenn Sie wissen, dass die GC der ersten Algo nicht während der zweiten Algo aufgetreten ist, wodurch die Ergebnisse verzerrt werden.
Diese Art der Profilerstellung ist das einzige Mal, dass ich jedem empfehlen würde, manuell Daten zu sammeln.
Auf jeden Fall ein erfundenes Beispiel
Ein möglicher Anwendungsfall ist, dass wenn Sie sehr große Objekte laden, diese in den Large Object Heap gelangen, der direkt an Gen 2 weitergeleitet wird. Gen 2 ist jedoch auch für langlebige Objekte gedacht, da es weniger häufig erfasst wird. Wenn Sie wissen, dass Sie kurzlebige Objekte aus irgendeinem Grund in Gen 2 laden, können Sie sie schneller entfernen, um Ihr Gen 2 kleiner und seine Sammlungen schneller zu halten.
Dies ist das beste Beispiel, das ich mir einfallen lassen könnte, und es ist nicht gut - der LOH-Druck, den Sie hier aufbauen, würde zu häufigeren Sammlungen führen, und Sammlungen sind so häufig, wie es ist schnell wie Sie es mit temporären Objekten ausblasen. Ich traue mich einfach nicht zu, eine bessere Erfassungshäufigkeit als die GC selbst anzunehmen - abgestimmt von Leuten, die weitaus schlauer sind als ich.
Sprechen wir also über einige der Semantiken und Mechanismen in .NET GC ... oder ..
Alles , was ich zu wissen glaube , über .NET GC
Bitte, wer hier Fehler findet - korrigiert mich. Ein Großteil der GCs ist als schwarze Magie bekannt, und obwohl ich versucht habe, Einzelheiten, über die ich mir nicht sicher war, auszulassen, habe ich wahrscheinlich immer noch einiges falsch gemacht.
Im Folgenden fehlen absichtlich zahlreiche Details, über die ich nicht sicher bin, sowie eine viel größere Menge an Informationen, die mir einfach nicht bekannt sind. Verwenden Sie diese Informationen auf eigenes Risiko.
GC-Konzepte
Der .NET-GC tritt zu inkonsistenten Zeiten auf, weshalb er als "nicht deterministisch" bezeichnet wird. Dies bedeutet, dass Sie sich nicht darauf verlassen können, dass er zu bestimmten Zeiten auftritt. Es ist auch ein Garbage Collector für Generationen, dh, es unterteilt Ihre Objekte in die Anzahl der GC-Durchläufe, die sie durchlaufen haben.
Objekte im Gen 0-Heap haben 0 Sammlungen durchlaufen. Diese wurden neu erstellt. Daher ist seit ihrer Instanziierung in letzter Zeit keine Sammlung mehr aufgetreten. Objekte in Ihrem Gen-1-Haufen haben einen Sammelpass durchlaufen, und Objekte in Ihrem Gen-2-Haufen haben ebenfalls 2 Sammelpass durchlaufen.
Es lohnt sich jetzt zu erwähnen, warum diese spezifischen Generationen und Partitionen entsprechend qualifiziert werden. Der .NET-GC erkennt nur diese drei Generationen, da die Auflistungsübergänge, die über diese drei Heaps gehen, alle geringfügig unterschiedlich sind. Einige Objekte überleben die Sammlung möglicherweise tausende Male. Der GC belässt diese lediglich auf der anderen Seite der Gen 2-Heap-Partition. Es macht keinen Sinn, sie irgendwo weiter zu partitionieren, da sie tatsächlich Gen 44 sind. Die Weitergabe der Sammlung ist die gleiche wie in Gen 2 Heap.
Diese spezifischen Generationen verfolgen semantische Zwecke sowie implementierte Mechanismen, die diese berücksichtigen, und auf diese werde ich gleich eingehen.
Was ist in einer Sammlung
Das Grundkonzept eines GC-Sammlungsdurchlaufs besteht darin, dass jedes Objekt in einem Heap-Bereich überprüft wird, um festzustellen, ob noch Live-Verweise (GC-Wurzeln) auf diese Objekte vorhanden sind. Wenn ein GC-Stammverzeichnis für ein Objekt gefunden wird, bedeutet dies, dass aktuell ausgeführter Code dieses Objekt weiterhin erreichen und verwenden kann, sodass es nicht gelöscht werden kann. Wenn jedoch kein GC-Stammverzeichnis für ein Objekt gefunden wird, benötigt der ausgeführte Prozess das Objekt nicht mehr, sodass es entfernt werden kann, um Speicher für neue Objekte freizugeben.
Jetzt, nachdem ein Haufen Gegenstände aufgeräumt und einige in Ruhe gelassen wurden, gibt es einen unglücklichen Nebeneffekt: Freiraumlücken zwischen lebenden Objekten, in denen die toten entfernt wurden. Wenn diese Speicherfragmentierung in Ruhe gelassen wird, wird einfach Speicher verschwendet. In der Regel werden in Sammlungen die so genannten "Komprimierungsvorgänge" ausgeführt, bei denen alle verbleibenden Live-Objekte im Heap zusammengepresst werden, sodass der freie Speicher auf einer Seite des Heaps für Gen zusammenhängend ist 0.
Nachdem wir nun die Idee von drei Haufen Speicher haben, die alle nach der Anzahl der durchlaufenen Sammlungsdurchläufe unterteilt sind, wollen wir uns über die Gründe für diese Partitionen unterhalten.
Sammlung Gen 0
Da Gen 0 das absolut neueste Objekt ist, ist es in der Regel sehr klein, sodass Sie es sicher und häufig sammeln können . Die Häufigkeit stellt sicher, dass der Heap klein bleibt und die Sammlungen sehr schnell sind, da sie sich über einem so kleinen Heap sammeln. Dies basiert mehr oder weniger auf einer Heuristik, die besagt: Ein Großteil der von Ihnen erstellten temporären Objekte ist sehr temporär, so dass sie fast unmittelbar nach der Verwendung nicht mehr verwendet oder referenziert werden und somit gesammelt werden können.
Sammlung Gen 1
Da es sich bei Gen 1 um Objekte handelt, die nicht in diese sehr vorübergehende Kategorie von Objekten fallen, ist die Lebensdauer möglicherweise noch relativ kurz, da ein großer Teil der erstellten Objekte noch nicht lange verwendet wird. Aus diesem Grund sammelt Gen 1 auch ziemlich häufig und hält seinen Haufen wieder klein, damit seine Sammlungen schnell sind. Es wird jedoch davon ausgegangen, dass weniger Objekte temporär sind als Gen 0, sodass sie seltener als Gen 0 erfasst werden
Ich werde sagen, dass ich die technischen Mechanismen, die sich zwischen dem Sammelpass von Gen 0 und dem von Gen 1 unterscheiden, offen gesagt nicht kenne, wenn es überhaupt andere gibt als die Häufigkeit, mit der sie sammeln.
Gen 2 Sammlung
Gen 2 muss jetzt die Mutter aller Haufen sein, oder? Nun ja, das ist mehr oder weniger richtig. Hier leben alle Ihre permanenten Objekte - zum Beispiel das Objekt, in dem Sie Main()
leben, und alles, was darauf Main()
verweist, denn diese werden verwurzelt, bis Sie Main()
am Ende Ihres Prozesses zurückkehren.
Da Gen 2 im Grunde genommen ein Eimer für alles ist, was die anderen Generationen nicht sammeln konnten, sind seine Objekte größtenteils dauerhaft oder zumindest langlebig. Wenn Sie also nur sehr wenig von dem, was in Gen 2 enthalten ist, erkennen, können Sie es tatsächlich sammeln, da es nicht häufig gesammelt werden muss. Dadurch kann die Erfassung auch langsamer erfolgen, da sie viel seltener ausgeführt wird. Im Grunde haben sie hier alle zusätzlichen Verhaltensweisen für ungerade Szenarien in Angriff genommen, weil sie die Zeit haben, sie auszuführen.
Großer Objekthaufen
Ein Beispiel für das zusätzliche Verhalten von Gen 2 ist, dass es auch die Auflistung auf dem Large Object Heap ausführt. Bisher habe ich mich ausschließlich mit dem Small Object Heap befasst, aber die .NET-Laufzeit ordnet aufgrund der oben genannten Komprimierung Dinge mit bestimmten Größen einem separaten Heap zu. Für die Komprimierung müssen Objekte verschoben werden, wenn die Auflistung auf dem Small Object Heap abgeschlossen ist. Wenn es in Gen 1 ein lebendes 10-MB-Objekt gibt, wird es viel länger dauern, bis die Komprimierung nach der Sammlung abgeschlossen ist, wodurch die Sammlung von Gen 1 verlangsamt wird. Damit wird dieses 10-MB-Objekt dem Large Object Heap zugewiesen und während der Gen 2 gesammelt, die so selten ausgeführt wird.
Finalisierung
Ein weiteres Beispiel sind Objekte mit Finalisierern. Sie platzieren einen Finalizer für ein Objekt, das auf Ressourcen außerhalb des Geltungsbereichs von .NETs GC verweist (nicht verwaltete Ressourcen). Der Finalizer ist die einzige Möglichkeit für den GC, die Erfassung einer nicht verwalteten Ressource anzufordern. Sie implementieren Ihren Finalizer, um die manuelle Erfassung / Entfernung / Freigabe der nicht verwalteten Ressource durchzuführen und sicherzustellen, dass sie nicht aus Ihrem Prozess ausläuft. Wenn der GC den Finalizer für Ihre Objekte ausführt, löscht Ihre Implementierung die nicht verwaltete Ressource, sodass der GC in der Lage ist, Ihr Objekt zu entfernen, ohne ein Ressourcenleck zu riskieren.
Der Mechanismus, mit dem Finalizer dies tun, besteht darin, direkt in einer Finalisierungswarteschlange referenziert zu werden. Wenn die Laufzeit ein Objekt mit einem Finalizer zuweist, fügt sie der Finalisierungswarteschlange einen Zeiger auf dieses Objekt hinzu und sperrt das Objekt (als Fixieren bezeichnet), sodass es durch die Komprimierung nicht verschoben wird, wodurch die Finalisierungswarteschlangenreferenz unterbrochen wird. Wenn Sammlungsdurchläufe stattfinden, hat Ihr Objekt möglicherweise kein GC-Stammverzeichnis mehr, aber die Finalisierung muss ausgeführt werden, bevor es gesammelt werden kann. Wenn das Objekt also tot ist, verschiebt die Sammlung seine Referenz aus der Finalisierungswarteschlange und platziert eine Referenz darauf in der sogenannten "FReachable" -Warteschlange. Dann geht die Sammlung weiter. Zu einem anderen "nicht deterministischen" Zeitpunkt in der Zukunft, Ein separater Thread, der als Finalizer-Thread bezeichnet wird, durchläuft die FReachable-Warteschlange und führt die Finalizer für jedes der referenzierten Objekte aus. Nachdem es fertig ist, ist die FReachable-Warteschlange leer, und es wurde ein bisschen in die Kopfzeile jedes Objekts gekippt, das angibt, dass keine Finalisierung erforderlich ist. (Dieses Bit kann auch manuell mit gekippt werden.)GC.SuppressFinalize
die gemeinsam ist Dispose()
Methoden), habe ich auch den Verdacht hat es die Objekte steckte los, aber zitieren Sie mich nicht daran. Die nächste Sammlung, die sich auf dem Haufen befindet, in dem sich dieses Objekt befindet, wird es endlich sammeln. Die Sammlungen von Gen 0 berücksichtigen Objekte mit dem für die Finalisierung erforderlichen Bit nicht einmal, sondern befördern sie automatisch, ohne auch nur nach ihrer Wurzel zu suchen. Ein unbewurzeltes Objekt, das in Gen 1 FReachable
finalisiert werden muss , wird in die Warteschlange geworfen , aber die Sammlung macht nichts anderes damit, sodass es in Gen 2 lebt. Auf diese Weise werden alle Objekte, die einen Finalizer haben, und nicht GC.SuppressFinalize
wird in Gen 2 gesammelt.