La mémoire en C# sous le microscope : comment fonctionne le ramasse-miettes et pourquoi c'est important

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

La magie que vous devez comprendre

\n\n

Quiconque a écrit en C# ne serait-ce que quelques semaines le sait : la mémoire est gérée par l'environnement d'exécution (CLR). Vous créez simplement des objets avec new, et quand ils ne sont plus nécessaires — le ramasse-miettes (GC) arrive et nettoie tout. Cela ressemble à de la magie, n'est-ce pas ? Mais derrière cette magie se cache une ingénierie rigoureuse, et si vous ne la comprenez pas — votre application commencera à « ralentir », à « consommer » des gigaoctets de RAM ou même plantera avec OutOfMemoryException.

\n\n

Dans cet article, nous allons regarder sous le capot du CLR et comprendre comment fonctionne exactement la gestion de la mémoire en C#. Vous apprendrez en quoi la pile diffère du tas, ce que sont les générations d'objets, pourquoi IDisposable est votre meilleur ami, et comment éviter les pièges avec le Large Object Heap. C'est parti !

\n\n

Pile et tas : les fondements dont on ne parle pas dans les manuels

\n\n

Lorsque votre programme s'exécute, le CLR divise la mémoire en deux zones principales : la pile (Stack) et le tas (Heap). Comprendre la différence entre elles est la base, sans laquelle tout le reste perd son sens.

\n\n

La pile — rapide, mais petite

\n

La pile est une zone mémoire où sont stockés les types valeur (int, bool, struct, enum) et les références vers les objets. Elle fonctionne selon le principe LIFO (Last In, First Out). Lorsque vous appelez une méthode, un cadre lui est alloué dans la pile. Dès que la méthode se termine — le cadre est détruit instantanément. Aucun ramasse-miettes n'est nécessaire ici. La vitesse d'accès à la pile est maximale, mais sa taille est limitée (généralement 1-4 Mo par thread).

\n\n
void Example()\n{\n    int x = 42;          // x se trouve dans la pile\n    string s = "Bonjour"; // la référence s dans la pile, l'objet lui-même — dans le tas\n}\n
\n\n

Le tas — plus lent, mais plus spacieux

\n

Le tas (Managed Heap) est la mémoire « durable ». C'est ici que vivent tous les types référence (classes, chaînes, tableaux, délégués). L'allocation mémoire dans le tas se produit à chaque new. Et c'est là que le ramasse-miettes entre en jeu. C'est lui qui décide quand la mémoire peut être libérée, et quand il faut attendre.

\n\n

Important : La pile n'est pas « mauvaise », et le tas n'est pas « bon ». Chacun a son rôle. Les types valeur sont rapides, mais on ne peut pas les passer par référence sans le mot-clé ref. Les types référence sont flexibles, mais nécessitent le GC.

\n\n

Comment fonctionne le ramasse-miettes en C# : générations et algorithme

\n\n

Le ramasse-miettes dans .NET n'est pas simplement « supprimer et oublier ». C'est un mécanisme intelligent qui utilise les générations (Generations) pour minimiser les pauses dans le fonctionnement de l'application.

\n\n

Trois générations d'objets

\n
    \n
  • Gen 0 — les objets nouveau-nés. Ils vivent peu de temps. Le nettoyage de Gen 0 est fréquent et très rapide.
  • \n
  • Gen 1 — les « survivants » après un nettoyage. Zone tampon entre les objets jeunes et anciens.
  • \n
  • Gen 2 — les objets de longue durée. Le nettoyage de Gen 2 est le plus coûteux et le plus rare. Si un objet survit jusqu'à Gen 2, il y restera probablement longtemps.
  • \n
\n\n

L'algorithme de fonctionnement du GC, simplifié, ressemble à ceci :

\n
    \n
  1. L'application demande de la mémoire. S'il n'y a pas de place dans le tas — un nettoyage est déclenché.
  2. \n
  3. Le GC construit un graphe d'accessibilité des objets, en partant des racines (champs statiques globaux, variables locales dans les piles des threads, registres CPU).
  4. \n
  5. Tous les objets inaccessibles sont marqués comme « déchets ».
  6. \n
  7. Un compactage (compaction) a lieu — les objets vivants sont déplacés pour éviter la fragmentation.
  8. \n
  9. La mémoire libérée est retournée au pool.
  10. \n
\n\n

Voici un exemple concret de l'impact du GC sur les performances :

\n\n
class HeavyObject\n{\n    public byte[] Data = new byte[1024 * 1024]; // 1 Mo\n}\n\nvoid TestGC()\n{\n    for (int i = 0; i < 1000; i++)\n    {\n        var obj = new HeavyObject(); // On alloue 1 Mo dans le tas à chaque fois\n        // obj sort de la portée — devient un déchet\n    }\n    // Après la boucle, le GC peut nettoyer Gen 0 rapidement                

Blogs

Book Recommendations