Müllsammler. Voller Kurs + Transfer von BOTR

In diesem Artikel finden Sie zwei Informationsquellen gleichzeitig:


  1. Schließe den Garbage Collector-Kurs in Russisch ab: CLRium # 6 ( aktueller Workshop hier )
  2. Übersetzung eines Artikels aus BOTR "Garbage Collector Device" von Maoni Stevens.


1. CLRium Nr. 5: Schließen Sie den Garbage Collector-Kurs ab



2. Garbage Collector Device von Maoni Stephens ( @ maoni0 )


Hinweis: Weitere Informationen zur Garbage Collection im Allgemeinen finden Sie im Garbage Collection-Handbuch . Spezielle Informationen zum Garbage Collector in der CLR finden Sie im Buch Pro .NET Memory Management . Links zu beiden Ressourcen finden Sie am Ende des Dokuments.


Komponentenarchitektur


Die Speicherbereinigung ist mit zwei Komponenten verbunden: einem Spender und einem Sammler. Der Allokator ist dafür verantwortlich, Speicher zuzuweisen und gegebenenfalls den Kollektor aufzurufen. Der Kollektor sammelt Müll oder Speicher von Objekten, die vom Programm nicht mehr verwendet werden.


Es gibt andere Möglichkeiten, den Kollektor beispielsweise manuell mit GC.Collect aufzurufen. Außerdem erhält der Finalizer-Thread möglicherweise eine asynchrone Benachrichtigung, dass der Speicher knapp wird (was den Kollektor verursacht).


Verteilergerät


Der Verteiler wird von Hilfskomponenten der Laufzeit mit folgenden Informationen aufgerufen:


  • die erforderliche Größe des zugewiesenen Grundstücks;
  • Kontext der Speicherzuweisung für den Ausführungsthread;
  • Flags, die beispielsweise angeben, ob das Objekt finalisierbar ist.

Der Garbage Collector bietet keine speziellen Verarbeitungsmethoden für verschiedene Objekttypen. Es erhält Informationen zur Größe des Objekts aus der Laufzeit.


Je nach Größe unterteilt der Kollektor Objekte in zwei Kategorien: klein (<85.000 Byte) und groß (> = 85.000 Byte). Im Allgemeinen kann die Montage kleiner und großer Objekte auf die gleiche Weise erfolgen. Der Kollektor trennt sie jedoch nach Größe, da das Komprimieren großer Objekte viele Ressourcen erfordert.


Der Garbage Collector weist dem Allokator basierend auf den Zuordnungskontexten Speicher zu. Die Größe des Zuordnungskontexts wird durch die Blöcke des zugewiesenen Speichers bestimmt.


  • Auswahlkontexte sind kleine Bereiche eines bestimmten Heap-Segments, von denen jeder für einen bestimmten Ausführungsfluss vorgesehen ist. Auf einer Maschine mit einem Prozessor (dh 1 logischer Prozessor) wird ein einzelner Speicherzuordnungskontext für Objekte der Generation 0 verwendet.


  • Block des zugewiesenen Speichers - Die vom Zuweiser jedes Mal zugewiesene Speichermenge, wenn mehr Speicher benötigt wird, um ein Objekt innerhalb des Bereichs zu positionieren. Die Blockgröße beträgt normalerweise 8 KB und die durchschnittliche Größe verwalteter Objekte beträgt 35 Byte. Daher können Sie in einem Block viele Objekte platzieren.



Große Objekte verwenden keine Kontexte und Blöcke. Ein großes Objekt kann größer sein als diese kleinen Speicherstücke. Darüber hinaus zeigen sich die Vorteile der Verwendung dieser Bereiche (siehe unten) nur bei der Arbeit mit kleinen Objekten. Der Platz für große Objekte wird direkt im Heap-Segment zugewiesen.


Der Verteiler ist so ausgelegt, dass:


  • Rufen Sie bei Bedarf den Garbage Collector auf: Der Allokator ruft den Kollektor auf, wenn die für Objekte zugewiesene Speichermenge den vom Kollektor festgelegten Schwellenwert überschreitet oder wenn der Allokator in diesem Segment keinen Speicher mehr zuordnen kann. Schwellenwerte und kontrollierte Segmente werden später ausführlich beschrieben.


  • Speicherort von Objekten speichern: Objekte, die sich zusammen in einem Segment des Heaps befinden, werden an nahe beieinander liegenden virtuellen Adressen gespeichert.


  • Verwenden Sie den Cache effizient: Der Allokator reserviert Speicher in Blöcken und nicht für jedes Objekt. Es wird so viel Speicher auf Null gesetzt, um den Prozessor-Cache vorzubereiten, da einige Objekte direkt darin platziert werden. Der Block des zugewiesenen Speichers beträgt normalerweise 8 KB.


  • Begrenzen Sie effektiv den dem Ausführungsthread zugewiesenen Bereich: Die Nähe der dem Thread zugewiesenen Kontexte und Speicherblöcke stellt sicher, dass nur ein Thread Daten in den zugewiesenen Speicherplatz schreibt. Infolgedessen muss die Speicherzuweisung nicht eingeschränkt werden, bis der Speicherplatz im aktuellen Zuordnungskontext beendet ist.


  • Sicherstellen der Speicherintegrität: Der Garbage Collector setzt den Speicher für neu zugewiesene Objekte immer zurück, damit ihre Verknüpfungen nicht auf beliebige Speicherabschnitte verweisen.


  • Heap-Kontinuität sicherstellen: Der Allokator erstellt aus dem verbleibenden Speicher in jedem zugewiesenen Block ein freies Objekt. Wenn beispielsweise 30 Bytes im Block verbleiben und 40 Bytes erforderlich sind, um das nächste Objekt aufzunehmen, wandelt der Allokator diese 30 Bytes in ein freies Objekt um und fordert einen neuen Speicherblock an.



API


Object* GCHeap::Alloc(size_t size,  DWORD); Object* GCHeap::Alloc(alloc_context* acontext, size_t size,  DWORD); 

Mit diesen Funktionen können Sie Speicher für kleine und große Objekte zuweisen. Es gibt eine Funktion zum Zuweisen von Speicherplatz direkt auf dem Haufen großer Objekte (LOH):


  Object* GCHeap::AllocLHeap(size_t size,  DWORD); 

Sammelvorrichtung


Garbage Collector-Aufgaben


GC ist für eine effiziente Speicherverwaltung ausgelegt. Entwickler, die verwalteten Code schreiben, können ihn ohne großen Aufwand verwenden. Gute Regierungsführung bedeutet:


  • Die Speicherbereinigung sollte häufig genug erfolgen, um den verwalteten Heap nicht mit einer großen Anzahl (nach Verhältnis oder in absoluter Menge) nicht verwendeter Objekte (Speicher) zu verschmutzen, für die Speicher zugewiesen ist.
  • Die Speicherbereinigung sollte so selten wie möglich erfolgen, um keine nützliche Prozessorzeit zu verschwenden, obwohl eine häufigere Erfassung weniger Speicherbedarf ermöglicht.
  • Die Speicherbereinigung sollte produktiv sein, denn wenn als Ergebnis der Assembly nur ein kleines Stück Speicher freigegeben wurde, waren sowohl die Assembly als auch die aufgewendete Prozessorzeit vergebens.
  • Die Speicherbereinigung sollte schnell erfolgen, da viele Workloads eine kurze Verzögerungszeit erfordern.
  • Entwickler, die verwalteten Code schreiben, müssen nicht viel über die Speicherbereinigung wissen, um eine effiziente Speichernutzung (im Vergleich zu ihrer Arbeitslast) zu erreichen.
  • Der Garbage Collector muss sich an die unterschiedliche Art der Speichernutzung anpassen.

Logische Beschreibung des verwalteten Heaps


Der CLR-Garbage Collector sammelt Objekte, die nach Generierung logisch getrennt sind. Nach dem Zusammenstellen von Objekten in Generation N werden die verbleibenden Objekte als zur Generation N + 1 gehörend markiert. Dieser Prozess wird als Förderung von Objekten über Generationen hinweg bezeichnet. Es gibt Ausnahmen in diesem Prozess, wenn es notwendig ist, ein Objekt an eine niedrigere Generation zu übertragen oder es überhaupt nicht voranzutreiben.


Bei kleinen Objekten ist der Haufen in drei Generationen unterteilt: gen0, gen1 und gen2. Für große Objekte gibt es nur eine Generation - gen3. Gen0 und Gen1 werden als kurzlebige Generationen bezeichnet (Objekte leben für kurze Zeit in ihnen).


Für eine Reihe kleiner Objekte bedeutet die Generationsnummer ihr Alter. Zum Beispiel ist gen0 die jüngste Generation. Dies bedeutet nicht, dass alle Objekte in gen0 jünger sind als Objekte in gen1 oder gen2. Es gibt Ausnahmen, die unten beschrieben werden. Das Zusammenbauen einer Generation bedeutet das Zusammenbauen von Objekten in dieser Generation sowie in all ihren jüngeren Generationen.


Theoretisch kann die Montage von großen und kleinen Objekten auf die gleiche Weise erfolgen. Da die Komprimierung großer Objekte jedoch viele Ressourcen erfordert, erfolgt ihre Montage auf andere Weise. Große Objekte sind nur in gen2 enthalten und werden aus Leistungsgründen nur während der Speicherbereinigung in dieser Generation gesammelt. Sowohl gen2 als auch gen3 können groß sein, und das Erstellen eines Objekts in kurzlebigen Generationen (gen0 und gen1) sollte nicht zu ressourcenintensiv sein.


Objekte werden in der jüngsten Generation platziert. Für kleine Objekte ist dies gen0 und für große Objekte gen3.


Physische Beschreibung des verwalteten Heaps


Ein verwalteter Heap besteht aus einer Reihe von Segmenten. Ein Segment ist ein fortlaufender Speicherblock, den das Betriebssystem an den Garbage Collector übergibt. Heap-Segmente sind in kleine und große Abschnitte unterteilt, um kleine und große Objekte aufzunehmen. Die Segmente jedes Heaps sind miteinander verbunden. Beim Laden der CLR ist mindestens ein Segment für ein kleines Objekt und eines für ein großes Objekt reserviert.


In jedem Haufen kleiner Objekte gibt es nur ein kurzlebiges Segment, in dem sich die Generationen gen0 und gen1 befinden. Dieses Segment kann Objekte zur Generierung von Gen2 enthalten oder nicht. Zusätzlich zu den kurzlebigen Segmenten können ein oder mehrere zusätzliche Segmente existieren, die Gen2-Segmente sein werden, da sie Objekte der Generation 2 enthalten.


Ein Stapel großer Objekte besteht aus einem oder mehreren Segmenten.


Das Heap-Segment wird von niedrigeren zu höheren Adressen gefüllt. Dies bedeutet, dass Objekte an den unteren Adressen des Segments älter sind als Objekte am Senior. Es gibt auch Ausnahmen, die unten beschrieben werden.


Heap-Segmente werden nach Bedarf zugewiesen. Wenn sie keine verwendeten Objekte enthalten, werden Segmente gelöscht. Das anfängliche Segment auf dem Heap ist jedoch immer vorhanden. Für jeden Heap wird jeweils ein Segment zugewiesen. Bei kleinen Objekten geschieht dies während der Speicherbereinigung und bei großen Objekten während der Speicherzuweisung für sie. Ein solches Schema erhöht die Produktivität, da große Objekte nur in Generation 2 zusammengesetzt werden (was viele Ressourcen erfordert).


Heap-Segmente werden in Auswahlen zusammengefügt. Das letzte Segment in der Kette ist immer kurzlebig. Segmente, in denen alle Objekte gesammelt werden, können beispielsweise als kurzlebig wiederverwendet werden. Die Wiederverwendung von Segmenten gilt nur für Haufen kleiner Objekte. Jedes Mal große Objekte aufnehmen, wenn die gesamte Gruppe großer Objekte berücksichtigt wird. Kleine Objekte werden nur in kurzlebigen Segmenten platziert.


Schwellenwert des zugewiesenen Speichers


Dies ist ein logisches Konzept, das sich auf die Größe jeder Generation bezieht. Wenn es überschritten wird, beginnt die Generierung mit der Speicherbereinigung.


Der Schwellenwert für eine bestimmte Generation wird in Abhängigkeit von der Anzahl der überlebenden Objekte in dieser Generation festgelegt. Wenn dieser Betrag hoch ist, wird der Schwellenwert höher. Es wird erwartet, dass das Verhältnis von verwendeten und nicht verwendeten Objekten während der Garbage Collection-Sitzung der nächsten Generation besser ist.


Generationsauswahl für die Speicherbereinigung


Bei Aktivierung muss der Kollektor bestimmen, in welcher Generation gebaut werden soll. Neben dem Schwellenwert beeinflussen weitere Faktoren diese Auswahl:


  • Fragmentierung einer Generation - Wenn eine Generation stark fragmentiert ist, ist die darin enthaltene Speicherbereinigung wahrscheinlich produktiv.
  • Wenn der Arbeitsspeicher des Geräts zu voll ist, kann der Kollektor eine gründlichere Reinigung durchführen. Wenn durch eine solche Bereinigung mit größerer Wahrscheinlichkeit Speicherplatz frei wird und unnötiger Seitenwechsel vermieden wird (Speicher im gesamten Computer).
  • Wenn einem kurzlebigen Segment der Speicherplatz ausgeht, kann der Kollektor eine gründlichere Reinigung in diesem Segment durchführen (mehr Objekte der Generation 1 sammeln), um die Zuweisung eines neuen Heap-Segments zu vermeiden.

Speicherbereinigungsprozess


Markierungsphase


Während dieser Phase sollte die CLR alle lebenden Objekte finden.


Der Vorteil eines Sammlers mit Unterstützung von Generationen ist seine Fähigkeit, Müll nur in einem Teil des Haufens zu reinigen, anstatt ständig alle Objekte zu beobachten. Beim Sammeln von Müll in kurzlebigen Generationen sollte der Kollektor von der Laufzeitumgebung Informationen darüber erhalten, welche Objekte in diesen Generationen noch vom Programm verwendet werden. Darüber hinaus können Objekte in älteren Generationen Objekte in jüngeren Generationen verwenden und sich auf diese beziehen.


Um alte Objekte zu markieren, die auf neue verweisen, verwendet der Garbage Collector spezielle Bits. Bits werden vom JIT-Compiler-Mechanismus während Zuweisungsvorgängen gesetzt. Wenn das Objekt zur kurzlebigen Erzeugung gehört, setzt der JIT-Compiler das Byte, das das Bit enthält, das die Anfangsposition angibt. Beim Sammeln von Müll in kurzlebigen Generationen kann der Kollektor diese Bits für den gesamten verbleibenden Heap verwenden und nur die Objekte anzeigen, denen diese Bits entsprechen.


Planungsphase


Zu diesem Zeitpunkt wird die Komprimierung modelliert, um ihre Wirksamkeit zu bestimmen. Wenn das Ergebnis produktiv ist, beginnt der Kollektor mit der eigentlichen Komprimierung. Ansonsten macht er nur die Reinigung.


Bühne bewegen


Wenn der Kollektor eine Komprimierung durchführt, werden die Objekte verschoben. In diesem Fall müssen Sie die Links zu diesen Objekten aktualisieren. Während der Verschiebungsphase muss der Kollektor alle Verknüpfungen finden, die auf Objekte in den Generationen verweisen, in denen die Speicherbereinigung stattfindet. Im Gegensatz dazu markiert der Kollektor während der Markierungsphase nur lebende Objekte, sodass er keine schwachen Glieder berücksichtigen muss.


Kompressionsstufe


Diese Phase ist recht einfach, da der Sammler bereits in der Planungsphase neue Adressen für sich bewegende Objekte festgelegt hat. Beim Komprimieren werden Objekte an diese Adressen kopiert.


Reinigungsphase


Während dieser Phase sucht der Sammler nach ungenutztem Raum zwischen lebenden Objekten. Anstelle dieses Raumes schafft er freie Objekte. Nicht verwendete Objekte in der Nähe werden zu einem freien Objekt. Alle freien Objekte werden in die Liste der freien Objekte aufgenommen .


Code-Fluss


Bedingungen:


  • WKS GC: Garbage Collection im Workstation-Modus
  • SVR GC: Garbage Collection im Servermodus

Funktionsverhalten


WKS GC ohne parallele Speicherbereinigung

  1. Der Benutzer-Thread verwendet den gesamten ihm zugewiesenen Speicher und ruft den Garbage Collector auf.
  2. Der Collector ruft SuspendEE auf, um alle verwalteten Threads SuspendEE .
  3. Der Sammler wählt eine Generation zur Reinigung.
  4. Das Markieren von Objekten beginnt.
  5. Der Kollektor geht in die Planungsphase und bestimmt den Komprimierungsbedarf.
  6. Bei Bedarf verschiebt der Kollektor Objekte und führt eine Komprimierung durch. In einem anderen Fall wird nur die Reinigung durchgeführt.
  7. Der Collector ruft RestartEE auf, um die verwalteten Threads RestartEE zu starten.
  8. Benutzer-Threads funktionieren weiterhin.

WKS GC mit paralleler Speicherbereinigung

Dieser Algorithmus beschreibt die Speicherbereinigung im Hintergrund.


  1. Der Benutzer-Thread verwendet den gesamten ihm zugewiesenen Speicher und ruft den Garbage Collector auf.
  2. Der Collector ruft SuspendEE auf, um alle verwalteten Threads SuspendEE .
  3. Der Kollektor bestimmt, ob die Hintergrund-Garbage Collection ausgeführt werden soll.
  4. In diesem Fall wird der Hintergrund-Garbage-Collection-Thread aktiviert. Dieser Thread ruft RestartEE auf, um verwaltete Threads RestartEE .
  5. Die Speicherzuweisung für verwaltete Prozesse wird gleichzeitig mit der Hintergrundbereinigung fortgesetzt.
  6. Ein Benutzer-Thread kann den gesamten ihm zugewiesenen Speicher verwenden und die kurzlebige Speicherbereinigung (auch als Speicherbereinigung mit hoher Priorität bezeichnet) starten. Es läuft genauso wie im Workstation-Modus ohne parallele Garbage Collection.
  7. Der Hintergrund-Garbage-Collection- SuspendEE ruft SuspendEE erneut auf, um die Markierung abzuschließen, und ruft dann RestartEE auf, um eine parallele Bereinigung mit laufenden Benutzerthreads zu starten.
  8. Die Hintergrund-Speicherbereinigung ist abgeschlossen.

SVR GC ohne parallele Speicherbereinigung

  1. Der Benutzer-Thread verwendet den gesamten ihm zugewiesenen Speicher und ruft den Garbage Collector auf.
  2. Garbage Collection-Threads im SuspendEE werden aktiviert und bewirken, dass SuspendEE die Ausführung verwalteter Threads SuspendEE .
  3. Garbage Collection-Streams führen im Servermodus dieselben Vorgänge wie im Workstation-Modus ohne parallele Garbage Collection aus.
  4. Garbage Collection-Threads im RestartEE rufen RestartEE auf, um verwaltete Threads zu starten.
  5. Benutzer-Threads funktionieren weiterhin.

SVR GC mit paralleler Speicherbereinigung

Der Algorithmus ist der gleiche wie bei der parallelen Speicherbereinigung im Workstation-Modus. In Server-Threads wird nur die Nicht-Phonon-Erfassung durchgeführt.


Physische Architektur


Dieser Abschnitt hilft Ihnen beim Verständnis des Codeflusses.


Wenn dem Benutzer-Thread der Speicherplatz ausgeht, kann er mithilfe der Funktion try_allocate_more_space freien Speicherplatz try_allocate_more_space .


Die Funktion try_allocate_more_space ruft GarbageCollectGeneration wenn Sie den Garbage Collector starten müssen.


Wenn die Garbage Collection im Workstation-Modus nicht parallel ist, wird GarbageCollectGeneration in dem vom Garbage Collector aufgerufenen Benutzer-Thread ausgeführt. Der Codestream lautet wie folgt:


  GarbageCollectGeneration() { SuspendEE(); garbage_collect(); RestartEE(); } garbage_collect() { generation_to_condemn(); gc1(); } gc1() { mark_phase(); plan_phase(); } plan_phase() { //   ,   //    if (compact) { relocate_phase(); compact_phase(); } else make_free_lists(); } 

Wenn die parallele Speicherbereinigung im Workstation-Modus (standardmäßig) ausgeführt wird, sieht der Code-Fluss für die Hintergrund-Speicherbereinigung folgendermaßen aus:


  GarbageCollectGeneration() { SuspendEE(); garbage_collect(); RestartEE(); } garbage_collect() { generation_to_condemn(); //     //      do_background_gc(); } do_background_gc() { init_background_gc(); start_c_gc (); //           . wait_to_proceed(); } bgc_thread_function() { while (1) { //    //  gc1(); } } gc1() { background_mark_phase(); background_sweep(); } 

Ressourcen-Links


Source: https://habr.com/ru/post/de463293/


All Articles