Chargement...

Gestion de la mémoire et Garbage Collector en C#

Apprenez la gestion de la mémoire et le garbage collector en C# pour comprendre le cycle de vie des objets.

Dans les applications .NET, la gestion de la mémoire (Memory Management) est effectuée automatiquement par le Garbage Collector (GC). Le développeur n’a pas besoin de gérer manuellement le cycle de vie des objets. Le GC détecte les objets inutilisés et libère la mémoire correspondante. Cependant, comprendre le fonctionnement de la gestion de la mémoire est essentiel pour optimiser les performances.


Qu’est-ce que la Gestion de la Mémoire ?

La gestion de la mémoire consiste à suivre le cycle de vie des objets créés par une application dans la RAM. Dans l’environnement .NET, il existe deux zones de mémoire principales :


Différences entre la Pile (Stack) et le Tas (Heap)

CaractéristiqueStackHeap
Espace MémoirePetit et rapideGrand, espace géré
Types StockésTypes valeurTypes référence
GestionAutomatique (basée sur la portée)Gérée par le Garbage Collector
Cycle de VieLibéré à la fin de la méthodeConservé jusqu’à ce que le GC le détecte
Vitesse d’AccèsTrès rapidePlus lente

Qu’est-ce que le Garbage Collector (GC) ?

Le Garbage Collector est un composant qui libère automatiquement les objets inutilisés du tas. Cela réduit le risque de fuite de mémoire (memory leak). Le GC s’exécute périodiquement pendant l’exécution du programme et libère les objets non référencés.


using System;

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

        Console.WriteLine("Avant le nettoyage de la mémoire...");
        GC.Collect(); // Appel manuel du GC
        Console.WriteLine("Après le nettoyage de la mémoire...");
    }
}

Appeler manuellement le GC est généralement déconseillé et ne doit être fait que dans des cas particuliers (par ex. tests ou opérations très consommatrices de mémoire).


Comment fonctionne le GC ?

Le Garbage Collector fonctionne selon un modèle basé sur les générations. Cela signifie que les objets à courte durée de vie et ceux à longue durée de vie sont stockés dans des zones différentes.

Le GC parcourt l’ensemble du tas et libère les objets qui ne sont plus accessibles depuis les références racines.


Différence entre Dispose et Finalize

Le GC ne suffit pas à lui seul pour une bonne gestion de la mémoire. Les ressources non managées (ex. fichiers, connexions réseau, objets GDI) ne sont pas automatiquement nettoyées par le GC. Pour ces ressources, on utilise l’interface IDisposable et la méthode Finalize (destructeur).


class RessourceFichier : IDisposable
{
    private bool disposed = false;

    public void Ecrire(string data)
    {
        if (disposed)
            throw new ObjectDisposedException(nameof(RessourceFichier));
        Console.WriteLine($"Écriture des données : {data}");
    }

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

    ~RessourceFichier()
    {
        Console.WriteLine("Finalize appelé.");
    }
}

// Utilisation :
using (var fichier = new RessourceFichier())
{
    fichier.Ecrire("Test");
}

La méthode Dispose() est utilisée pour le nettoyage manuel, tandis que Finalize (le destructeur) est appelé par le GC. SuppressFinalize() empêche le GC d’appeler le destructeur une seconde fois.


Qu’est-ce qu’une Fuite de Mémoire ?

Bien que le GC soit automatique, des fuites de mémoire peuvent survenir si les ressources sont mal gérées. Les gestionnaires d’événements ou références statiques non libérés peuvent empêcher la collecte des objets.


// Exemple classique de fuite de mémoire :
class Operation
{
    public event EventHandler DonneesPretes;
}

class Program
{
    static Operation op = new Operation();

    static void Main()
    {
        op.DonneesPretes += (s, e) => Console.WriteLine("L'événement reste abonné !");
        op = null; // L’événement maintient une référence, le GC ne peut pas nettoyer !
    }
}

Dans ce cas, les événements doivent être désinscrits manuellement avec -=.


Modes et Paramètres de Performance du GC

Le GC .NET dispose de deux principaux modes de fonctionnement :


// Exemple dans app.config ou runtimeconfig.json :
<configuration>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

La méthode GC.TryStartNoGCRegion() peut empêcher le GC de s’exécuter pendant une certaine période, garantissant un fonctionnement sans interruption dans les applications temps réel.


Surveiller les Événements du GC

Vous pouvez utiliser GCNotification et GC.GetTotalMemory() pour surveiller quand le GC s’exécute ou combien de mémoire a été libérée.


using System;

class Program
{
    static void Main()
    {
        long avant = GC.GetTotalMemory(false);
        var data = new byte[10_000_000];
        long apres = GC.GetTotalMemory(false);

        Console.WriteLine($"Différence : {(apres - avant) / 1024 / 1024} Mo");

        GC.RegisterForFullGCNotification(10, 10);
        GC.Collect();
        Console.WriteLine("GC déclenché !");
    }
}

Exemple : Traitement de Grandes Données

Dans les applications manipulant de gros volumes de données, créer inutilement des objets augmente la charge du GC. L’exemple suivant montre la réutilisation des objets avec ArrayPool<T> pour optimiser la mémoire.


using System;
using System.Buffers;

class Program
{
    static void Main()
    {
        var pool = ArrayPool<byte>.Shared;
        byte[] buffer = pool.Rent(1024 * 1024); // Louer un buffer de 1 Mo

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

        Console.WriteLine("Données traitées.");
        pool.Return(buffer); // Pas de charge supplémentaire pour le GC
    }
}

ArrayPool permet de réutiliser les objets fréquemment utilisés et réduit la pression sur le GC.


Performances et Bonnes Pratiques


TL;DR

  • Le Garbage Collector nettoie automatiquement les objets inutilisés sur le tas.
  • La pile (stack) contient des types valeur rapides et temporaires ; le tas (heap) stocke les types référence.
  • Le GC fonctionne selon un modèle à générations (0, 1, 2).
  • IDisposable fournit un nettoyage manuel pour les ressources non managées.
  • Les fuites de mémoire proviennent souvent d’événements ou de références statiques.
  • Utilisez des pools d’objets (ArrayPool, ObjectPool) pour de meilleures performances.

Articles connexes