Die Garbage Collection (GC) in Go ist ein automatischer Mechanismus zur Speicherverwaltung, der erstmals in den frühen Versionen der Sprache eingeführt wurde. Historisch gesehen war die GC in Go eine Quelle der Kritik aufgrund ihrer Auswirkungen auf die Leistung. Mit der Weiterentwicklung der Sprache, insbesondere nach Version Go 1.5, wurde sie jedoch erheblich verbessert: Jetzt wird ein dreifacher konkurrierender (concurrent, tricolor, mark-and-sweep) Müllsammler mit minimalen Pausen (low-pause GC) verwendet.
Das Problem entsteht, wenn Programme eine große Anzahl von temporären Objekten erstellen oder wenn sie keine Verweise auf nicht verwendete Strukturen entfernen: dies erhöht die Last für die GC und kann zu langen Pausen führen. Besonderes Augenmerk sollte auf Objektarten, zyklische Verweise und lange Verweisketten gelegt werden, die außerhalb des Stacks liegen.
Die Lösung besteht darin, die Speicherzuweisung zu überwachen, Profilierung zu verwenden und die GC über die Umgebungsvariable GOGC anzupassen, während die Anzahl der Allokationen in inneren Schleifen und kritischen Abschnitten minimiert wird. Es ist wichtig zu erinnern, dass die Garbage Collection in Go nur für den Heap funktioniert: Alles, was im Stack zugewiesen wird, wird automatisch bei Verlassen des Gültigkeitsbereichs gelöscht, während Objekte, die in den Heap "gehen", von der GC verwaltet werden.
Codebeispiel:
// Profilierung der Allokation und Optimierung der GC import ( "runtime" "fmt" ) func main() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) fmt.Printf("Vor der Allokation: %d bytes\n", memStats.Alloc) s := make([]int, 1_000_000) for i := range s { s[i] = i } runtime.GC() // manuelle Bereinigung runtime.ReadMemStats(&memStats) fmt.Printf("Nach der GC: %d bytes\n", memStats.Alloc) }
Wesentliche Merkmale:
GOGC konfiguriert werden (z.B. GOGC=100 - Standard; eine Senkung beschleunigt die GC, erhöht jedoch den CPU-Verbrauch).Wie kann man herausfinden, welches Objekt in den Heap geht und welches im Stack bleibt?
Antwort: Dafür wird die Escape-Analyse verwendet, die mit dem Compiler-Flag go build -gcflags="-m" analysiert werden kann. Objekte, die aus einer Funktion zurückgegeben oder in Closures verwendet werden, gehen häufig in den Heap.
Codebeispiel:
func escape() *int { v := 42 return &v // v wird im Heap sein }
Beeinflusst die GC alle Variablen, auch die im Stack?
Nein, die GC arbeitet nur mit dem Heap (heap-zugewiesene Objekte). Alles, was im Stack zugewiesen wird, wird automatisch beim Ende der Funktion gelöscht.
Kann ein manueller Aufruf von runtime.GC() die Leistung erheblich verbessern?
Im Gegenteil, ein manueller Aufruf verschlechtert oft die Leistung und erhöht den CPU-Verbrauch. Sollte nur für Tests oder debugging Fälle verwendet werden.
Negativer Fall
Ein Entwickler schreibt einen Dienst, der in jeder Anfrage neue große Slices erstellt, ohne über deren Lebensdauer nachzudenken. Dies führt schnell zu einer erhöhten Last auf der GC, zu großen Pausen und einer Verschlechterung der Leistung.
Vorteile:
Nachteile:
Positiver Fall
Ein Entwickler optimiert den Dienst, profilierte Allokationen, wiederverwendet Puffer durch sync.Pool, reduziert die Häufigkeit der GC-Aufrufe und minimiert die Speicherzuweisung in heißen Bereichen.
Vorteile:
Nachteile: