Wird geladen...

Speicherverwaltung und Garbage Collector in C#

Lernen Sie Speicherverwaltung und Garbage Collector in C#, um Objektlebenszyklen und Speicherbereinigung zu verstehen.

In .NET-Anwendungen wird das Speichermanagement automatisch vom Garbage Collector (GC) durchgeführt. Der Entwickler muss den Lebenszyklus von Objekten nicht manuell verwalten. Der GC erkennt ungenutzte Objekte und gibt den Speicher frei. Das Verständnis, wie das Speichermanagement funktioniert, ist jedoch entscheidend für Leistungsoptimierungen.


Was ist Speichermanagement?

Speichermanagement ist der Prozess der Verfolgung des Lebenszyklus von Objekten, die von einer Anwendung im RAM erstellt werden. In der .NET-Umgebung gibt es zwei Arten von Speicherbereichen:


Unterschied zwischen Stack und Heap

EigenschaftStackHeap
SpeicherbereichKlein und schnellGroß, verwalteter Bereich
Gespeicherte TypenWerttypenReferenztypen
VerwaltungAutomatisch (Scope-basiert)Vom Garbage Collector verwaltet
LebenszyklusWird gelöscht, wenn die Methode endetBleibt erhalten, bis der GC sie erkennt
ZugriffsgeschwindigkeitSehr schnellLangsamer

Was ist der Garbage Collector (GC)?

Der Garbage Collector ist eine Komponente, die ungenutzte Objekte im Heap automatisch bereinigt. Dadurch wird das Risiko von Memory Leaks reduziert. Der GC wird während der Programmausführung in bestimmten Abständen aktiviert und gibt Objekte frei, auf die nicht mehr verwiesen wird.


using System;

class Program
{
    static void Main()
    {
        for (int i = 0; i < 1000; i++)
        {
            var daten = new byte[1024 * 1024]; // 1 MB
        }

        Console.WriteLine("Vor der Speicherbereinigung...");
        GC.Collect(); // Manuelles Auslösen des GC
        Console.WriteLine("Nach der Speicherbereinigung...");
    }
}

Das manuelle Aufrufen des GC wird im Allgemeinen nicht empfohlen und sollte nur in speziellen Fällen (z. B. Tests oder nach speicherintensiven Operationen) erfolgen.


Wie funktioniert der GC?

Der Garbage Collector arbeitet nach dem Prinzip der generationenbasierten Sammlung. Das bedeutet, dass kurzlebige und langlebige Objekte in unterschiedlichen Bereichen gespeichert werden.

Der GC durchsucht den gesamten Heap und gibt Objekte frei, die nicht mehr über Root-Referenzen erreichbar sind.


Unterschied zwischen Dispose und Finalize

Der GC allein reicht für das Speichermanagement nicht aus. Unverwaltete Ressourcen (z. B. Dateien, Netzwerkverbindungen, GDI-Objekte) werden nicht automatisch vom GC bereinigt. Für solche Objekte sollten die IDisposable-Schnittstelle und die Finalize-Methode verwendet werden.


class DateiRessource : IDisposable
{
    private bool disposed = false;

    public void Schreiben(string daten)
    {
        if (disposed)
            throw new ObjectDisposedException(nameof(DateiRessource));
        Console.WriteLine($"Daten werden geschrieben: {daten}");
    }

    public void Dispose()
    {
        if (!disposed)
        {
            Console.WriteLine("Ressource freigegeben (Dispose).");
            disposed = true;
            GC.SuppressFinalize(this);
        }
    }

    ~DateiRessource()
    {
        Console.WriteLine("Finalize aufgerufen.");
    }
}

// Verwendung:
using (var datei = new DateiRessource())
{
    datei.Schreiben("Test");
}

Die Methode Dispose() wird für manuelle Bereinigung verwendet, während Finalize (Destruktor) vom GC aufgerufen wird. SuppressFinalize() verhindert, dass der GC den Destruktor erneut aufruft.


Was ist ein Memory Leak?

Obwohl der GC automatisch arbeitet, können Speicherlecks auftreten, wenn Ressourcen falsch verwaltet werden. Besonders große Event-Handler oder statische Referenzen können verhindern, dass Objekte freigegeben werden.


// Klassisches Beispiel für ein Memory Leak:
class Prozess
{
    public event EventHandler DatenBereit;
}

class Program
{
    static Prozess p = new Prozess();

    static void Main()
    {
        p.DatenBereit += (s, e) => Console.WriteLine("Event blieb verbunden!");
        p = null; // Der GC kann nicht bereinigen, da das Event noch eine Referenz hält!
    }
}

In solchen Fällen sollten Events manuell mit -= abgemeldet werden.


GC-Modi und Leistungseinstellungen

Der .NET-GC hat zwei Hauptbetriebsmodi:


// Beispiel in app.config oder runtimeconfig.json:
<configuration>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

Die Methode GC.TryStartNoGCRegion() kann den GC für eine bestimmte Zeit deaktivieren. Dies sorgt für unterbrechungsfreien Betrieb in Echtzeitanwendungen.


GC-Ereignisse überwachen

Methoden wie GCNotification und GC.GetTotalMemory() können verwendet werden, um zu überwachen, wann der GC ausgeführt wird oder wie viel Speicher freigegeben wurde.


using System;

class Program
{
    static void Main()
    {
        long vorher = GC.GetTotalMemory(false);
        var daten = new byte[10_000_000];
        long nachher = GC.GetTotalMemory(false);

        Console.WriteLine($"Differenz: {(nachher - vorher) / 1024 / 1024} MB");

        GC.RegisterForFullGCNotification(10, 10);
        GC.Collect();
        Console.WriteLine("GC ausgelöst!");
    }
}

Beispiel: Verarbeitung großer Datenmengen

In Anwendungen, die mit großen Datenmengen arbeiten, erhöht das Erstellen unnötiger Objekte die Last des GC. Das folgende Beispiel zeigt die Wiederverwendung von Objekten mit ArrayPool<T> zur Speicheroptimierung.


using System;
using System.Buffers;

class Program
{
    static void Main()
    {
        var pool = ArrayPool<byte>.Shared;
        byte[] puffer = pool.Rent(1024 * 1024); // 1 MB Puffer anmieten

        for (int i = 0; i < puffer.Length; i++)
            puffer[i] = 255;

        Console.WriteLine("Daten verarbeitet.");
        pool.Return(puffer); // Keine zusätzliche GC-Belastung
    }
}

ArrayPool ermöglicht die Wiederverwendung häufig genutzter Objekte und reduziert die GC-Belastung.


Leistung und bewährte Praktiken


TL;DR

  • Der Garbage Collector bereinigt ungenutzte Objekte im Heap automatisch.
  • Der Stack speichert schnelle, kurzlebige Werttypen; der Heap speichert Referenztypen.
  • Der GC arbeitet nach einem Generationenmodell (0, 1, 2).
  • IDisposable ermöglicht manuelle Bereinigung unverwalteter Ressourcen.
  • Speicherlecks entstehen häufig durch Events oder statische Referenzen.
  • Für bessere Leistung können Objektpools (ArrayPool, ObjectPool) verwendet werden.

Ähnliche Artikel