Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
LangagesIntermédiaireSérie : C# / .NET

C# : Le Garbage Collector

Comment le GC nettoie la mémoire automatiquement, les générations, IDisposable, using et les pièges à éviter.

4 min de lecture
Mis à jour il y a 4 mois
c#csharpgarbage collectorGCmémoireIDisposableusing

C# : Le Garbage Collector

En C ou C++, le développeur doit libérer la mémoire manuellement (free(), delete). Oublie une libération, c'est une fuite mémoire. Libère deux fois, c'est un crash. En C#, tout ça est automatique grâce au garbage collector (GC).

Si tu n'as pas encore lu l'article sur la mémoire, commence par là - il explique la différence entre stack et heap, qui est essentielle pour comprendre ce que le GC nettoie.


# Comment ça marche

Le GC surveille le heap. La stack se nettoie toute seule (les variables locales disparaissent quand la méthode se termine), mais les objets sur le heap restent tant que quelqu'un les référence.

Quand un objet n'est plus accessible par aucune variable (directement ou indirectement), il est considéré comme "mort" et sera collecté.

void CreerVoiture()
{
    Voiture v = new Voiture("Peugeot", "208", 2022);
    Console.WriteLine(v.Marque);
}  // v sort du scope  -  plus aucune référence vers l'objet Voiture
 
// À un moment, le GC récupérera la mémoire occupée par cette Voiture

Le GC ne s'exécute pas immédiatement après qu'un objet devient inaccessible. Il attend le bon moment (quand la mémoire commence à se remplir) pour minimiser l'impact sur les performances.

Avant et après le Garbage Collector  -  les objets sans référence sont supprimés, les autres compactés
Avant et après le Garbage Collector - les objets sans référence sont supprimés, les autres compactés


# Les générations

Le GC ne parcourt pas tout le heap à chaque nettoyage - ce serait trop lent. Il fonctionne par générations, basé sur une observation simple : la plupart des objets meurent jeunes.

GénérationContientFréquence de nettoyage
Gen 0Objets nouvellement créésTrès fréquente
Gen 1Objets ayant survécu à un GC Gen 0Modérée
Gen 2Objets à longue durée de vieRare

Le cycle de vie d'un objet

  1. Un objet est créé -> il va en Gen 0
  2. Le GC nettoie la Gen 0 -> si l'objet est encore référencé, il est promu en Gen 1
  3. Le GC nettoie la Gen 1 -> s'il survit encore, il passe en Gen 2
  4. La Gen 2 n'est nettoyée que rarement (c'est coûteux car elle contient beaucoup d'objets)

Les variables temporaires, résultats intermédiaires et objets éphémères meurent en Gen 0 - nettoyage rapide, peu d'impact. Les caches, singletons et objets applicatifs vivent en Gen 2 - rarement touchés.


# Ce que le GC ne fait PAS

Le GC libère la mémoire, mais il ne gère pas les ressources externes :

  • Fichiers ouverts
  • Connexions réseau ou base de données
  • Handles système (sockets, threads natifs...)

Si tu crées un objet qui ouvre un fichier et que tu attends le GC pour le fermer, le fichier peut rester verrouillé pendant un temps indéterminé. C'est là qu'interviennent IDisposable et using.


# IDisposable et using

Le pattern IDisposable

Les classes qui gèrent des ressources externes implémentent l'interface IDisposable. Elle expose une seule méthode : Dispose(), qui libère les ressources proprement.

StreamReader reader = new StreamReader("fichier.txt");
string contenu = reader.ReadToEnd();
reader.Dispose();  // ferme le fichier explicitement

Le problème : si une exception est levée avant Dispose(), le fichier n'est jamais fermé.

using - la solution

using garantit que Dispose() est appelé quoi qu'il arrive, même en cas d'exception. C'est l'équivalent d'un try/finally avec Dispose() dans le finally.

// Syntaxe classique (bloc)
using (StreamReader reader = new StreamReader("fichier.txt"))
{
    string contenu = reader.ReadToEnd();
}  // reader.Dispose() est appelé automatiquement ici
 
// Syntaxe simplifiée (C# 8+)  -  Dispose à la fin du scope
using StreamReader reader = new StreamReader("fichier.txt");
string contenu = reader.ReadToEnd();
// reader.Dispose() est appelé à la fin de la méthode

Règle simple : si une classe implémente IDisposable, utilise toujours using. Ne compte jamais sur le GC pour fermer des fichiers ou des connexions.


# Les pièges courants

N'appelle jamais GC.Collect()

// Ne fais JAMAIS ça
GC.Collect();

Le GC est optimisé pour choisir le bon moment. Forcer une collection dégrade les performances car ça force un nettoyage complet, potentiellement sur les 3 générations. Dans 99.9% des cas, le GC fait un meilleur travail que toi.

Attention aux références cachées

Un objet n'est collecté que s'il est totalement inaccessible. Des références cachées peuvent le maintenir en vie :

// Piège : l'event handler garde une référence vers l'objet
button.Click += maMethode;
// Même si tu n'utilises plus l'objet, le bouton le maintient en vie
// Solution : se désabonner quand on n'en a plus besoin
button.Click -= maMethode;

Les objets statiques vivent éternellement

static List<Voiture> historique = new List<Voiture>();
 
void TraiterVoiture(Voiture v)
{
    historique.Add(v);  // v ne sera JAMAIS collectée
}

Les champs static vivent aussi longtemps que l'application. Si tu stockes des objets dans une collection statique sans jamais les retirer, c'est une fuite mémoire.


# La suite

Tu comprends maintenant comment C# gère le cycle de vie des objets en mémoire. Les prochains articles de la série :