Automatic Memory Management

Bei der automatischen Speicherverwaltung handelt es sich um einen Dienst, der von der Common Language Runtime während der verwalteten Ausführung zur Verfügung gestellt wird. Der Garbage Collector der Common Language Runtime verwaltet die Belegung und Freigabe von Arbeitsspeicher für eine Anwendung. Das bedeutet, dass beim Entwickeln verwalteter Anwendungen kein Code für Aufgaben der Speicherverwaltung geschrieben werden muss. Mithilfe der automatischen Speicherverwaltung können häufig auftretende Probleme beseitigt werden, z. B. wenn ein Objekt versehentlich nicht freigegeben wurde und dadurch Speicherverluste entstehen oder wenn auf den Speicher für ein Objekt zugegriffen wird, das bereits freigegeben wurde. In diesem Abschnitt wird beschrieben, wie Sie den Garbage Collector zum Belegen und Freigeben von Arbeitsspeicher verwenden können.

Speicherbelegung

Wenn Sie einen neuen Prozess initialisieren, wird diesem durch die Common Language Runtime ein zusammenhängender Adressraum reserviert. Dieser reservierte Adressraum wird als verwalteter Heap bezeichnet. Im verwalteten Heap steht ein Zeiger zur Verfügung, der auf die Adresse des nächsten im Heap zu speichernden Objekts zeigt. Anfangs ist dieser Zeiger auf die Basisadresse des verwalteten Heaps eingestellt. Alle Referenztypen werden im verwalteten Heap zugewiesen. Wurde von einer Anwendung der erste Referenztyp erstellt, wird für diesen Typ an der Basisadresse des verwalteten Heaps Speicherplatz belegt. Wenn das nächste Objekt von der Anwendung erstellt wird, belegt der Garbage Collector Speicherplatz im Adressraum direkt hinter dem ersten Objekt. Solange ein Adressbereich verfügbar ist, fährt der Garbage Collector auf diese Weise mit der Belegung von Arbeitsspeicher für neue Objekte fort.

Das Belegen von Speicher im verwalteten Heap beansprucht weniger Zeit als die nicht verwaltete Speicherbelegung. Da durch die Common Language Runtime Speicher für ein Objekt durch Hinzufügen eines Werts zu einem Zeiger belegt wird, ist diese Methode fast so schnell wie das Reservieren von Speicher im Stapel. Außerdem werden neue Objekte, für die Speicher reserviert wird, ständig nacheinander im verwalteten Heap gespeichert. Dadurch kann eine Anwendung auf diese Objekte sehr schnell zugreifen.

Freigeben von Arbeitsspeicher

Durch die Optimierungs-Engine des Garbage Collectors wird der beste Zeitpunkt für das Ausführen einer Garbage Collection bestimmt, die auf den erfolgten Speicherbelegungen basiert. Beim Ausführen einer Garbage Collection wird Speicher freigegeben, den von der Anwendung nicht mehr benötigte Objekte beanspruchen. Durch Überprüfen der Stammelemente der Anwendung wird ermittelt, welche Objekte nicht mehr verwendet werden. Jede Anwendung verfügt über einen Satz von Stammelementen. Jedes Stammelement bezieht sich entweder auf ein Objekt im verwalteten Heap oder ist auf NULL festgelegt. Die Stammelemente einer Anwendung beinhalten statische Felder, lokale Variablen und Parameter im Stapel des Threads sowie CPU-Register. Der Garbage Collector hat Zugriff auf eine Liste der aktiven Stammelemente, die vom JIT-Compiler (Just-In-Time) und der Common Language Runtime verwaltet wird. Mithilfe dieser Liste werden die Stammelemente einer Anwendung durchsucht, und es wird ein Diagramm erstellt, in dem alle von den Stammelementen aus erreichbaren Objekte enthalten sind.

Objekte, die nicht in diesem Diagramm aufgeführt werden, können von den Stammelementen aus nicht erreicht werden. Diese nicht erreichbaren Objekte werden vom Garbage Collector als Abfall betrachtet, und der von diesen Objekten belegte Speicherplatz wird wieder freigegeben. Während einer Garbage Collection wird der verwaltete Heap nach den Blöcken des Adressraums durchsucht, in denen sich nicht erreichbare Objekte befinden. Beim Auffinden dieser Objekte werden die erreichbaren Objekte mithilfe einer Speicherkopierfunktion im Speicher komprimiert, und die von den nicht erreichbaren Objekten belegten Blöcke des Adressraums werden freigegeben. Nach dem Komprimieren des Speichers für die erreichbaren Objekte werden vom Garbage Collector die erforderlichen Korrekturen am Zeiger vorgenommen, sodass die Stammelemente der Anwendung auf die neuen Speicherorte der Objekte verweisen. Außerdem wird der Zeiger des verwalteten Heaps auf die Position hinter dem letzten erreichbaren Objekt gesetzt. Beachten Sie, dass der Speicher nur dann komprimiert wird, wenn in der Garbage Collection eine signifikante Anzahl nicht erreichbarer Objekte gefunden wird. Wenn nach einer Garbage Collection alle Objekte in einem verwalteten Heap verbleiben, bedarf es auch keiner Speicherkomprimierung.

Um die Leistung zu verbessern, wird für große Objekte durch die Common Language Runtime Speicherplatz in einem separaten Heap belegt. Der Speicherplatz für große Objekte wird durch den Garbage Collector automatisch freigegeben. Um das Verschieben großer Objekte im Speicher zu vermeiden, wird dieser Speicher jedoch nicht komprimiert.

Generationen und Leistungsfähigkeit

Zur Optimierung der Leistung des Garbage Collectors wird der verwaltete Heap in drei Generationen unterteilt: 0, 1 und 2. Der Garbage Collection-Algorithmus der Laufzeit basiert auf mehreren Verallgemeinerungen, die von der Computersoftwareindustrie durch Experimentieren mit Garbage Collection-Schemas als gültig erkannt wurden. Erstens kann die Garbage Collection schneller ausgeführt werden, wenn der Speicher nur in einem Teil des verwalteten Heaps komprimiert wird und nicht im gesamten Heap. Zweitens ist die Lebensdauer von neueren Objekten kürzer als die von älteren Objekten. Drittens tendieren neuere Objekte dazu, miteinander in Beziehung zu stehen, und die Anwendung greift etwa zur gleichen Zeit auf sie zu.

Vom Garbage Collector der Laufzeit werden neue Objekte in Generation 0 gespeichert. Objekte, die früh in der Lebensdauer der Anwendung erstellt wurden und nach den Garbage Collections noch vorhanden sind, werden höher gestuft und werden in Generationen 1 und 2 gespeichert. Der Prozess der Objektheraufstufung wird weiter unten in diesem Thema beschrieben. Da es weniger Zeit beansprucht, statt des gesamten verwalteten Heaps nur einen Teil davon zu komprimieren, ist es bei diesem Konzept auch möglich, bei einer Garbage Collection lediglich Speicher einer bestimmten Generationsstufe freizugeben und nicht den des gesamten verwalteten Heaps.

Tatsächlich wird eine Garbage Collection erst dann eingeleitet, wenn der Bereich von Generation 0 voll ist. Versucht eine Anwendung, ein neues Objekt zu erzeugen, und der Bereich der Generation 0 ist voll, wird vom Garbage Collector ermittelt, dass es in diesem Bereich keinen freien Adressraum mehr gibt, der dem neuen Objekt zugewiesen werden kann. Daraufhin wird eine Garbage Collection gestartet, um im Bereich der Generation 0 freien Adressraum zu schaffen. Dabei werden nur die Objekte im Bereich der Generation 0 untersucht und nicht alle Objekte im verwalteten Heap. Das ist der effizienteste Weg, da neue Objekte zu einer kürzeren Lebensdauer tendieren. Es ist daher zu erwarten, dass beim Starten der Garbage Collection viele Objekte im Bereich der Generation 0 nicht mehr von der Anwendung benötigt werden. Außerdem liefert eine Garbage Collection im Bereich der Generation 0 oftmals ausreichend Speicherplatz, sodass von der Anwendung weitere neue Objekte erzeugt werden können.

Nach Durchführung einer Garbage Collection im Bereich der Generation 0 wird der Speicher für die erreichbaren Objekte komprimiert, wie weiter oben unter Freigeben von Arbeitsspeicher erläutert. Der Garbage Collector stuft dann diese Objekte höher und betrachtet diesen Teil des verwalteten Heaps als Generation 1. Da Objekte, die nach Garbage Collections noch vorhanden sind, normalerweise längere Lebensdauern haben, ist es sinnvoll, sie auf eine höhere Generation heraufzustufen. Auf diese Weise müssen die Objekte der Generationen 1 und 2 nicht bei jeder Garbage Collection, die für Objekte der Generation 0 durchgeführt wird, erneut vom Garbage Collector untersucht werden.

Nachdem der Garbage Collector die erste Garbage Collection der Generation 0 ausgeführt und die erreichbaren Objekte auf Generation 1 hochgestuft hat, betrachtet er den Rest des verwalteten Heaps als Generation 0. Er fährt fort, Speicher für neue Objekte in Generation 0 zu belegen, bis Generation 0 voll ist und eine weitere Garbage Collection ausgeführt werden muss. An diesem Punkt wird von der Optimierungs-Engine des Garbage Collectors ermittelt, ob auch die Objekte der älteren Generationen untersucht werden müssen. Wenn eine Garbage Collection der Generation 0 z. B. nicht genug Arbeitsspeicher für die Anwendung freigibt, um das Erstellen eines neuen Objekts erfolgreich auszuführen, kann der Garbage Collector eine Garbage Collection der Generation 1, dann Generation 2, ausführen. Wenn so nicht genug Arbeitsspeicher freigegeben wird, kann der Garbage Collector eine Garbage Collection der Generationen 2, 1 und 0 ausführen. Nach jeder Garbage Collection komprimiert der Garbage Collector die erreichbaren Objekte in Generation 0 und stuft sie auf Generation 1 herauf. Objekte in Generation 1, die nach den Garbage Collections noch vorhanden sind, werden auf Generation 2 hochgestuft. Da der Garbage Collector nur drei Generationen unterstützt, bleiben Objekte in Generation 2, die nach einer Garbage Collection noch vorhanden sind, in Generation 2, bis sie in einer späteren Garbage Collection als nicht erreichbar erkannt werden.

Freigeben von Speicher für nicht verwaltete Ressourcen

Für die meisten von einer Anwendung erzeugten Objekte führt der Garbage Collector alle Aufgaben der Speicherverwaltung automatisch aus. Allerdings ist bei nicht verwalteten Ressourcen explizites Bereinigen erforderlich. Die häufigsten nicht verwalteten Ressourcen sind Objekte, die eine Betriebssystemressource umschließen, z. B. ein Dateihandle, ein Fensterhandle oder eine Netzwerkverbindung. Obwohl der Garbage Collector die Lebensdauer eines verwalteten Objekts, das eine nicht verwaltete Ressource kapselt, verfolgen kann, stehen ihm keine genauen Informationen zum Bereinigen dieser Ressource zur Verfügung. Wenn Sie ein Objekt erstellen möchten, das eine nicht verwaltete Ressource kapselt, ist es ratsam, Code zum Bereinigen der nicht verwalteten Ressource in einer öffentlichen Dispose-Methode bereitzustellen. Durch Bereitstellen der Dispose-Methode wird Benutzern des Objekts die Möglichkeit gegeben, nicht mehr benötigten Speicherplatz explizit freizugeben, sobald das Objekt nicht mehr verwendet wird. Wenn Sie ein Objekt verwenden, das eine nicht verwaltete Ressource kapselt, sollten Sie die Dispose-Methode beachten und bei Bedarf aufrufen. Weitere Informationen zum Bereinigen nicht verwalteter Ressourcen und ein Beispiel für ein Entwurfsmuster zum Implementieren von Dispose finden Sie unter Garbage Collection

Siehe auch