Begriff
Garbage Collection (GC)
Warum wichtig?
Dieser Begriff ist ein Knoten im SengakujiWorks-Wissensnetz. Nutze Level 0 für die erste Einordnung, Level 1 für Praxis, Level 2 für technische Struktur und Level 3 für Grenzen, Fallstricke und Expertenkontext.
In C++ musst du Speicher selbst aufräumen.
"Ich brauche Platz für ein Bild." -> malloc().
"Ich brauche das Bild nicht mehr." -> free().
Vergisst du free(), läuft der RAM voll (Memory Leak).
Machst du free() zu früh, stürzt das Programm ab.
Moderne Sprachen (Java, Python, JS, Go) haben eine Müllabfuhr (Garbage Collector).
Der GC läuft im Hintergrund.
Er schaut: "Welche Daten werden von niemandem mehr benutzt?"
Und gibt den Speicher automatisch frei.
Du musst dich um nichts kümmern. (Meistens).
Merksatz: Ein automatischer Mechanismus zur Speicherverwaltung, der nicht mehr benötigten Speicherplatz identifiziert und freigibt, um Speicherlecks zu verhindern.
Gar nicht. Er ist einfach da. Aber du merkst ihn: Manchmal ruckelt dein Spiel oder dein Server kurz. Das ist ein GC Pause ("Stop the World"). Der GC ruft: "HALT! Niemand bewegt sich! Ich muss aufräumen." Wenn er fertig ist, läuft das Programm weiter.
1. Mark and Sweep
Der klassische Algorithmus.
- Mark: Der GC startet bei den "Roots" (Globale Variablen, aktiver Stack). Er folgt allen Referenzen und markiert jedes gefundene Objekt als "lebendig".
- Sweep: Er geht durch den ganzen Heap. Alles, was nicht markiert ist, ist Müll (unreachable) und wird gelöscht.
2. Generational GC (Young vs. Old)
Beobachtung: Die meisten Objekte sterben jung (lokale Variablen in einer Funktion). Der Heap wird geteilt:
- Eden Space (Young Gen): Hier entstehen neue Objekte. Wird sehr oft und schnell aufgeräumt (Minor GC).
- Old Gen: Objekte, die mehrere GCs überleben, werden hierhin befördert ("Tenured"). Wird selten aufgeräumt (Major GC), dauert aber länger. Das optimiert die Performance enorm.
3. Reference Counting (Python/Swift)
Alternative zu Mark-and-Sweep.
jedes Objekt hat einen Zähler: "Wie viele Variablen zeigen auf mich?"
a = Objekt (Count 1). b = a (Count 2). del a (Count 1). del b (Count 0 -> Löschen!).
Nachteil: Zirkuläre Referenzen. A zeigt auf B, B zeigt auf A. Count ist nie 0, obwohl niemand sie braucht. Python braucht einen extra GC, um diese Zyklen zu finden.
Tracing GCs und Concurrent Mark-and-Sweep (CMS)
Historisch pausierten Garbage Collectors die Applikation radikal, bis sie fertig waren ("Stop-The-World" / STW). Ein Server mit 32 GB RAM hing dann manchmal für 4 Sekunden im Nirvana (Fatality für Echtzeit-APIs oder Games). Moderne Architekturen (wie der V8 Orinoco in JavaScript oder der Java G1 / ZGC) arbeiten Concurrent (nebenläufig). Der "Mark"-Schritt sucht Roots während dein Applikations-Code normal auf anderen CPU-Kernen (Threads) weiterläuft. Die Magie hierbei sind Write Barriers. Da der App Code während der Such-Iteration Referenzen ändern kann (z.B. den Pointer eines Objekts spontan löschen), greift der Write Barrier als tief verankerter Kernel-Hook oder Compiler-Trick ein. Jedes "Umlegen" eines Speicher-Zeigers loggt er in einen dreckigen Puffer, den der GC am Ende abfragt (Dijkstra's Tricolor-Algorithmus), um seine Markierungen extrem schnell und ohne lange STW-Freezes final upzudaten.
GC Roots / Reachability Tree
Wo fängt ein Mark & Sweep Algorithmus an zu graben? Die Wurzeln (Roots) sind Objekte, die absolut garantiert aus Speichersicht immer "am Leben" sind (der Mount Everest der App). Dazu gehören global deklarierte Klassenvariablen, fest importierte Module (Window in JS), C++ JNI Handler und – extrem wichtig – alle Variablen, die im Execution Context der aktuellen CPU-Stacks (im Thread-Speicher) liegen. Jede Funktion, die nicht beendet ist (Closure!), behält ihre lokalen Variablen als Root bei. Erst wenn ein Objekt im Heap keinerlei Pfad mehr zu einem dieser Roots zurückverfolgen kann, gilt es als "Unreachable Islands" und wird zum Abschuss markiert (Sweep).
Memory Compaction und Pinned Objects
Wenn Objekte kreuz und quer freigegeben (gelöscht) werden, entsteht Fragmentation im "Old Gen" Heap. Der RAM hat Löcher wie Schweizer Käse.
Wenn du jetzt ein riesiges Objekt (z.B. 100 MB Bitmap) laden willst, stürzt das System per OOM (Out of Memory) ab, da kein zusammenhängender, kontinuierlicher 100 MB Block frei ist, obwohl netto vielleicht 2 GB an Fragmenten leer sind.
Moderne GCs (z.B. C# LOH oder Go's Runtime) betreiben Memory Compaction. Der Heap wird defragmentiert, lebende Objekte werden physisch verschoben und aneinandergereiht.
Das ist hochkomplex: Wenn ein Objekt verschoben wird, müssen auf einen Schlag zehntausende verweisende Pointer quer über den RAM geupdatet werden (pointer rewriting), ohne dass der App Code crasht! Ausnahmen (z. B. durch C-Code gebundene Arrays) nennt man Pinned Objects, diese dürfen vom GC-Compactor unmöglich verschoben werden.
Quick-Check
System.gc()?
In Java kannst du den GC rufen. Aber das ist nur ein "Vorschlag". Die JVM entscheidet selbst, ob sie Lust hat. Man sollte es nie tun.Rust?
Rust hat keinen GC, ist aber trotzdem Memory Safe. Es nutzt das "Ownership Model". Der Compiler weiß genau, wann eine Variable "out of scope" geht und fügt denfree()Befehl automatisch dort ein. Beste Performance ohne GC-Pausen.Finalizer?
Eine Methode, die aufgerufen wird, bevor das Objekt stirbt. (Wie Destruktor). Ist extrem unzuverlässig (wird vielleicht nie aufgerufen) und bremst den GC. Vermeiden.